@kubb/middleware-barrel 5.0.0-beta.75 → 5.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -86,39 +86,14 @@ function compareByPath(a, b) {
86
86
  return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
87
87
  }
88
88
  //#endregion
89
- //#region src/utils/excludedPaths.ts
90
- /**
91
- * Builds a POSIX-normalized prefix for a plugin's output directory, with a trailing `/`.
92
- *
93
- * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
94
- */
95
- function getPluginOutputPrefix(plugin, config) {
96
- return `${toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))}/`;
97
- }
98
- /**
99
- * Returns `true` when `filePath` lives under any of the given excluded directory prefixes.
100
- *
101
- * Both sides are POSIX-normalized so Windows backslash paths match correctly.
102
- */
103
- function isExcludedPath(filePath, prefixes) {
104
- const normalized = toPosixPath(filePath);
105
- return prefixes.values().some((prefix) => normalized.startsWith(prefix));
106
- }
107
- //#endregion
108
- //#region src/constants.ts
109
- /**
110
- * Full file name for barrel files (with extension).
111
- */
112
- const BARREL_FILENAME = "index.ts";
113
- //#endregion
114
- //#region src/utils/getBarrelFiles.ts
89
+ //#region src/utils.ts
115
90
  const SOURCE_EXTENSIONS = new Set([
116
91
  ".ts",
117
92
  ".tsx",
118
93
  ".js",
119
94
  ".jsx"
120
95
  ]);
121
- const BARREL_SUFFIX = `/${BARREL_FILENAME}`;
96
+ const BARREL_SUFFIX = `/index.ts`;
122
97
  function toRelativeModulePath(fromDir, filePath) {
123
98
  return `./${filePath.slice(fromDir.length + 1)}`;
124
99
  }
@@ -127,7 +102,7 @@ function isBarrelPath(path) {
127
102
  }
128
103
  function makeBarrel(dirPath, exports) {
129
104
  return createFile({
130
- baseName: BARREL_FILENAME,
105
+ baseName: "index.ts",
131
106
  path: `${dirPath}${BARREL_SUFFIX}`,
132
107
  exports,
133
108
  sources: [],
@@ -200,8 +175,9 @@ function walkAllOrNamed(node, params, isRoot, out) {
200
175
  }
201
176
  /**
202
177
  * Recursive walk that emits one barrel per directory, re-exporting files and sub-barrels.
178
+ * Used when nested: true.
203
179
  */
204
- function walkPropagate(node, out) {
180
+ function walkNested(node, out) {
205
181
  const exports = [];
206
182
  for (const child of node.children) {
207
183
  if (child.isFile) {
@@ -209,7 +185,7 @@ function walkPropagate(node, out) {
209
185
  exports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }));
210
186
  continue;
211
187
  }
212
- walkPropagate(child, out);
188
+ walkNested(child, out);
213
189
  exports.push(createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }));
214
190
  }
215
191
  if (exports.length > 0) out.push(makeBarrel(node.path, exports));
@@ -234,13 +210,13 @@ function indexRelevantFiles(files, outputPath) {
234
210
  /**
235
211
  * Generates barrel `FileNode`s for the directory rooted at `outputPath`.
236
212
  */
237
- function getBarrelFiles({ outputPath, files, barrelType, recursive = false }) {
213
+ function getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }) {
238
214
  const { sourceFiles, paths } = indexRelevantFiles(files, outputPath);
239
215
  if (paths.length === 0) return [];
240
216
  const tree = buildTree(outputPath, paths);
241
217
  const result = [];
242
- if (barrelType === "propagate") {
243
- walkPropagate(tree, result);
218
+ if (nested) {
219
+ walkNested(tree, result);
244
220
  return result;
245
221
  }
246
222
  const strategy = LEAF_STRATEGIES.get(barrelType);
@@ -252,13 +228,30 @@ function getBarrelFiles({ outputPath, files, barrelType, recursive = false }) {
252
228
  }, true, result);
253
229
  return result;
254
230
  }
231
+ /**
232
+ * Builds a POSIX-normalized prefix for a plugin's output directory, with a trailing `/`.
233
+ *
234
+ * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
235
+ */
236
+ function getPluginOutputPrefix(plugin, config) {
237
+ return `${toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))}/`;
238
+ }
239
+ /**
240
+ * Returns `true` when `filePath` lives under any of the given excluded directory prefixes.
241
+ *
242
+ * Both sides are POSIX-normalized so Windows backslash paths match correctly.
243
+ */
244
+ function isExcludedPath(filePath, prefixes) {
245
+ const normalized = toPosixPath(filePath);
246
+ return prefixes.values().some((prefix) => normalized.startsWith(prefix));
247
+ }
255
248
  //#endregion
256
249
  //#region src/middleware.ts
257
250
  /**
258
251
  * Generates `index.ts` barrel files for each plugin and a root barrel at `config.output.path/index.ts`.
259
252
  *
260
- * Each plugin inherits `output.barrelType` from `config.output.barrelType` (defaults to `'named'`).
261
- * Set `barrelType: false` on a plugin to disable its barrel and exclude it from the root barrel.
253
+ * Each plugin inherits `output.barrel` from `config.output.barrel` (defaults to `{ type: 'named' }`).
254
+ * Set `barrel: false` on a plugin to disable its barrel and exclude it from the root barrel.
262
255
  *
263
256
  * @example
264
257
  * ```ts
@@ -266,9 +259,9 @@ function getBarrelFiles({ outputPath, files, barrelType, recursive = false }) {
266
259
  * import { middlewareBarrel } from '@kubb/middleware-barrel'
267
260
  *
268
261
  * export default defineConfig({
269
- * output: { path: 'src/gen', barrelType: 'named' },
262
+ * output: { path: 'src/gen', barrel: { type: 'named' } },
270
263
  * plugins: [
271
- * pluginTs({ output: { path: 'types', barrelType: 'all' } }),
264
+ * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
272
265
  * pluginZod({ output: { path: 'schemas' } }),
273
266
  * ],
274
267
  * middleware: [middlewareBarrel()],
@@ -285,11 +278,22 @@ const middlewareBarrel = defineMiddleware(() => {
285
278
  name: middlewareBarrelName,
286
279
  hooks: {
287
280
  "kubb:plugin:end"({ plugin, config, files, upsertFile }) {
288
- const barrelType = plugin.options.output?.barrelType ?? config.output.barrelType ?? "named";
289
- if (!barrelType) {
281
+ const pluginBarrel = plugin.options.output?.barrel;
282
+ const configBarrel = config.output.barrel;
283
+ const defaultBarrel = { type: "named" };
284
+ let barrelConfig;
285
+ if (pluginBarrel !== void 0) barrelConfig = pluginBarrel;
286
+ else if (configBarrel !== void 0) barrelConfig = configBarrel === false ? false : {
287
+ ...configBarrel,
288
+ nested: false
289
+ };
290
+ else barrelConfig = defaultBarrel;
291
+ if (barrelConfig === false) {
290
292
  excludedPrefixes.add(getPluginOutputPrefix(plugin, config));
291
293
  return;
292
294
  }
295
+ const barrelType = barrelConfig.type;
296
+ const nested = barrelConfig.nested ?? false;
293
297
  const base = resolve(config.root, config.output.path);
294
298
  const target = resolve(base, plugin.options.output.path);
295
299
  const relative = path.relative(base, target);
@@ -298,19 +302,21 @@ const middlewareBarrel = defineMiddleware(() => {
298
302
  outputPath: target,
299
303
  files,
300
304
  barrelType,
305
+ nested,
301
306
  recursive: true
302
307
  });
303
308
  if (barrelFiles.length > 0) upsertFile(...barrelFiles);
304
309
  },
305
310
  "kubb:plugins:end"({ files, config, upsertFile }) {
306
- const rootBarrelType = config.output.barrelType ?? "named";
311
+ const barrelConfig = config.output.barrel ?? { type: "named" };
307
312
  const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes));
308
313
  excludedPrefixes.clear();
309
- if (!rootBarrelType) return;
314
+ if (barrelConfig === false) return;
315
+ const barrelType = barrelConfig.type;
310
316
  const rootBarrelFiles = getBarrelFiles({
311
317
  outputPath: resolve(config.root, config.output.path),
312
318
  files: filteredFiles,
313
- barrelType: rootBarrelType
319
+ barrelType
314
320
  });
315
321
  if (rootBarrelFiles.length > 0) upsertFile(...rootBarrelFiles);
316
322
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/path.ts","../../../internals/utils/src/buildTree.ts","../src/utils/excludedPaths.ts","../src/constants.ts","../src/utils/getBarrelFiles.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n","import { toPosixPath } from './path.ts'\n\n/**\n * A node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\nexport type BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n const childIndex = new Map<BuildTree, Map<string, BuildTree>>()\n childIndex.set(root, new Map())\n\n const rootPrefix = `${normalizedRoot}/`\n\n for (const filePath of filePaths) {\n const normalized = toPosixPath(filePath)\n if (!normalized.startsWith(rootPrefix)) continue\n\n const parts = normalized.slice(rootPrefix.length).split('/')\n if (parts.length === 0) continue\n\n let current = root\n const lastIndex = parts.length - 1\n for (const [i, part] of parts.entries()) {\n if (!part) continue\n\n const isLast = i === lastIndex\n const siblings = childIndex.get(current)!\n let child = siblings.get(part)\n if (!child) {\n child = { path: `${current.path}/${part}`, children: [], isFile: isLast }\n current.children.push(child)\n siblings.set(part, child)\n if (!isLast) childIndex.set(child, new Map())\n }\n current = child\n }\n }\n\n sortTree(root)\n\n return root\n}\n\nfunction sortTree(node: BuildTree): void {\n if (node.children.length === 0) return\n node.children.sort(compareByPath)\n for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n","import { resolve } from 'node:path'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { toPosixPath } from '@internals/utils'\n\n/**\n * Builds a POSIX-normalized prefix for a plugin's output directory, with a trailing `/`.\n *\n * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.\n */\nexport function getPluginOutputPrefix(plugin: NormalizedPlugin, config: Config): string {\n return `${toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))}/`\n}\n\n/**\n * Returns `true` when `filePath` lives under any of the given excluded directory prefixes.\n *\n * Both sides are POSIX-normalized so Windows backslash paths match correctly.\n */\nexport function isExcludedPath(filePath: string, prefixes: ReadonlySet<string>): boolean {\n const normalized = toPosixPath(filePath)\n return prefixes.values().some((prefix) => normalized.startsWith(prefix))\n}\n","/**\n * Full file name for barrel files (with extension).\n */\nexport const BARREL_FILENAME = 'index.ts' as const\n","import { extname } from 'node:path'\nimport { createExport, createFile } from '@kubb/ast'\nimport type { ExportNode, FileNode, SourceNode } from '@kubb/ast'\nimport { BARREL_FILENAME } from '../constants.ts'\nimport type { BarrelType } from '../types.ts'\nimport { type BuildTree, buildTree, toPosixPath } from '@internals/utils'\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst BARREL_SUFFIX = `/${BARREL_FILENAME}`\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {\n return createFile({\n baseName: BARREL_FILENAME,\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports,\n sources: [],\n imports: [],\n // Barrel files must never carry a banner or footer: they only re-export\n // symbols and adding a directive like \"use server\" would break consumers.\n banner: undefined,\n footer: undefined,\n })\n}\n\ntype LeafContext = {\n dirPath: string\n leafPath: string\n sourceFile: FileNode | undefined\n}\n\ntype LeafStrategy = (ctx: LeafContext) => Array<ExportNode>\n\nfunction hasOnlyNonIndexableSources(sources: ReadonlyArray<SourceNode>): boolean {\n if (sources.length === 0) return false\n for (const source of sources) {\n if (source.isIndexable) return false\n }\n return true\n}\n\nfunction partitionIndexableNames(sources: ReadonlyArray<SourceNode>): Map<boolean, Set<string>> {\n const byTypeOnly = new Map<boolean, Set<string>>([\n [false, new Set()],\n [true, new Set()],\n ])\n for (const source of sources) {\n if (!source.isIndexable || !source.name) continue\n byTypeOnly.get(Boolean(source.isTypeOnly))!.add(source.name)\n }\n return byTypeOnly\n}\n\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return []\n return [createExport({ path: toRelativeModulePath(dirPath, leafPath) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath(dirPath, leafPath)\n\n if (!sourceFile) return [createExport({ path: modulePath })]\n\n const namesByTypeOnly = partitionIndexableNames(sourceFile.sources)\n const valueNames = namesByTypeOnly.get(false)!\n const typeNames = namesByTypeOnly.get(true)!\n\n if (valueNames.size === 0 && typeNames.size === 0) {\n if (sourceFile.sources.length > 0) return []\n return [createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(createExport({ name: [...typeNames].sort(), path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\nconst LEAF_STRATEGIES: ReadonlyMap<Exclude<BarrelType, 'propagate'>, LeafStrategy> = new Map([\n ['all', allStrategy],\n ['named', namedStrategy],\n])\n\ntype LeafWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n recursive: boolean\n}\n\n/**\n * Post-order walk that emits a barrel per visited directory.\n */\nfunction walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean, out: Array<FileNode>): Array<string> {\n const subtreeLeaves: Array<string> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)\n continue\n }\n\n const childLeaves = walkAllOrNamed(child, params, false, out)\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !params.recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))\n\n if (exports.length > 0) {\n out.push(makeBarrel(node.path, exports))\n }\n\n return subtreeLeaves\n}\n\n/**\n * Recursive walk that emits one barrel per directory, re-exporting files and sub-barrels.\n */\nfunction walkPropagate(node: BuildTree, out: Array<FileNode>): void {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }))\n continue\n }\n\n walkPropagate(child, out)\n exports.push(createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n\n if (exports.length > 0) {\n out.push(makeBarrel(node.path, exports))\n }\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Absolute directory the barrel(s) should be rooted at.\n * Only files living under this path are considered.\n */\n outputPath: string\n /**\n * Pool of generated files to scan for indexable sources.\n */\n files: ReadonlyArray<FileNode>\n /**\n * Re-export style used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n * - `'propagate'` emits one barrel per directory and chains sub-barrels\n */\n barrelType: BarrelType\n /**\n * Also generate a barrel for each sub-directory of `outputPath`.\n * No effect for `barrelType: 'propagate'`, which always recurses.\n *\n * @default false\n */\n recursive?: boolean\n}\n\n/**\n * Generates barrel `FileNode`s for the directory rooted at `outputPath`.\n */\nexport function getBarrelFiles({ outputPath, files, barrelType, recursive = false }: GetBarrelFilesParams): Array<FileNode> {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n if (paths.length === 0) return []\n\n const tree = buildTree(outputPath, paths)\n const result: Array<FileNode> = []\n\n if (barrelType === 'propagate') {\n walkPropagate(tree, result)\n return result\n }\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return result\n\n walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, true, result)\n return result\n}\n","import path from 'node:path'\nimport { resolve } from 'node:path'\nimport { defineMiddleware } from '@kubb/core'\nimport type { Middleware } from '@kubb/core'\nimport type { BarrelType, RootBarrelType } from './types.ts'\nimport { getPluginOutputPrefix, isExcludedPath } from './utils/excludedPaths.ts'\nimport { getBarrelFiles } from './utils/getBarrelFiles.ts'\n\ndeclare global {\n namespace Kubb {\n interface PluginOptionsRegistry {\n output: {\n /**\n * Re-export style for this plugin's barrel file.\n * Set to `false` to disable barrel generation for this plugin entirely; doing so also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrelType` when omitted.\n *\n * @default 'named'\n */\n barrelType?: BarrelType | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Re-export style for the root barrel file at `config.output.path/index.ts`.\n * Set to `false` to disable root barrel generation. Individual plugins can override\n * this via their own `output.barrelType`.\n *\n * `'propagate'` is not available here — it only applies at the per-plugin level.\n *\n * @default 'named'\n */\n barrelType?: RootBarrelType | false\n }\n }\n }\n}\n\n/**\n * Generates `index.ts` barrel files for each plugin and a root barrel at `config.output.path/index.ts`.\n *\n * Each plugin inherits `output.barrelType` from `config.output.barrelType` (defaults to `'named'`).\n * Set `barrelType: false` on a plugin to disable its barrel and exclude it from the root barrel.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@kubb/core'\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 */\n\n/**\n * Stable string identifier for the barrel middleware.\n */\nexport const middlewareBarrelName = 'middleware-barrel' satisfies Middleware['name']\n\nexport const middlewareBarrel = defineMiddleware(() => {\n const excludedPrefixes = new Set<string>()\n\n return {\n name: middlewareBarrelName,\n hooks: {\n 'kubb:plugin:end'({ plugin, config, files, upsertFile }) {\n const barrelType = plugin.options.output?.barrelType ?? config.output.barrelType ?? 'named'\n\n if (!barrelType) {\n excludedPrefixes.add(getPluginOutputPrefix(plugin, config))\n return\n }\n\n const base = resolve(config.root, config.output.path)\n const target = resolve(base, plugin.options.output.path)\n const relative = path.relative(base, target)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('Invalid output path')\n }\n const barrelFiles = getBarrelFiles({\n outputPath: target,\n files,\n barrelType,\n recursive: true,\n })\n\n if (barrelFiles.length > 0) {\n upsertFile(...barrelFiles)\n }\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const rootBarrelType = config.output.barrelType ?? 'named'\n\n const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n if (!rootBarrelType) return\n\n const rootBarrelFiles = getBarrelFiles({\n outputPath: resolve(config.root, config.output.path),\n files: filteredFiles,\n barrelType: rootBarrelType,\n })\n\n if (rootBarrelFiles.length > 0) {\n upsertFile(...rootBarrelFiles)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/path.ts","../../../internals/utils/src/buildTree.ts","../src/utils.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n","import { toPosixPath } from './path.ts'\n\n/**\n * A node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\nexport type BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n const childIndex = new Map<BuildTree, Map<string, BuildTree>>()\n childIndex.set(root, new Map())\n\n const rootPrefix = `${normalizedRoot}/`\n\n for (const filePath of filePaths) {\n const normalized = toPosixPath(filePath)\n if (!normalized.startsWith(rootPrefix)) continue\n\n const parts = normalized.slice(rootPrefix.length).split('/')\n if (parts.length === 0) continue\n\n let current = root\n const lastIndex = parts.length - 1\n for (const [i, part] of parts.entries()) {\n if (!part) continue\n\n const isLast = i === lastIndex\n const siblings = childIndex.get(current)!\n let child = siblings.get(part)\n if (!child) {\n child = { path: `${current.path}/${part}`, children: [], isFile: isLast }\n current.children.push(child)\n siblings.set(part, child)\n if (!isLast) childIndex.set(child, new Map())\n }\n current = child\n }\n }\n\n sortTree(root)\n\n return root\n}\n\nfunction sortTree(node: BuildTree): void {\n if (node.children.length === 0) return\n node.children.sort(compareByPath)\n for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n","import { extname, resolve } from 'node:path'\nimport { createExport, createFile } from '@kubb/ast'\nimport type { ExportNode, FileNode, SourceNode } from '@kubb/ast'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { type BuildTree, buildTree, toPosixPath } from '@internals/utils'\nimport type { BarrelType } from './types.ts'\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst BARREL_SUFFIX = `/index.ts`\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {\n return createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports,\n sources: [],\n imports: [],\n // Barrel files must never carry a banner or footer: they only re-export\n // symbols and adding a directive like \"use server\" would break consumers.\n banner: undefined,\n footer: undefined,\n })\n}\n\ntype LeafContext = {\n dirPath: string\n leafPath: string\n sourceFile: FileNode | undefined\n}\n\ntype LeafStrategy = (ctx: LeafContext) => Array<ExportNode>\n\nfunction hasOnlyNonIndexableSources(sources: ReadonlyArray<SourceNode>): boolean {\n if (sources.length === 0) return false\n for (const source of sources) {\n if (source.isIndexable) return false\n }\n return true\n}\n\nfunction partitionIndexableNames(sources: ReadonlyArray<SourceNode>): Map<boolean, Set<string>> {\n const byTypeOnly = new Map<boolean, Set<string>>([\n [false, new Set()],\n [true, new Set()],\n ])\n for (const source of sources) {\n if (!source.isIndexable || !source.name) continue\n byTypeOnly.get(Boolean(source.isTypeOnly))!.add(source.name)\n }\n return byTypeOnly\n}\n\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return []\n return [createExport({ path: toRelativeModulePath(dirPath, leafPath) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath(dirPath, leafPath)\n\n if (!sourceFile) return [createExport({ path: modulePath })]\n\n const namesByTypeOnly = partitionIndexableNames(sourceFile.sources)\n const valueNames = namesByTypeOnly.get(false)!\n const typeNames = namesByTypeOnly.get(true)!\n\n if (valueNames.size === 0 && typeNames.size === 0) {\n if (sourceFile.sources.length > 0) return []\n return [createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(createExport({ name: [...typeNames].sort(), path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\nconst LEAF_STRATEGIES: ReadonlyMap<BarrelType, LeafStrategy> = new Map([\n ['all', allStrategy],\n ['named', namedStrategy],\n])\n\ntype LeafWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n recursive: boolean\n}\n\n/**\n * Post-order walk that emits a barrel per visited directory.\n */\nfunction walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean, out: Array<FileNode>): Array<string> {\n const subtreeLeaves: Array<string> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)\n continue\n }\n\n const childLeaves = walkAllOrNamed(child, params, false, out)\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !params.recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))\n\n if (exports.length > 0) {\n out.push(makeBarrel(node.path, exports))\n }\n\n return subtreeLeaves\n}\n\n/**\n * Recursive walk that emits one barrel per directory, re-exporting files and sub-barrels.\n * Used when nested: true.\n */\nfunction walkNested(node: BuildTree, out: Array<FileNode>): void {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }))\n continue\n }\n\n walkNested(child, out)\n exports.push(createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n\n if (exports.length > 0) {\n out.push(makeBarrel(node.path, exports))\n }\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Absolute directory the barrel(s) should be rooted at.\n * Only files living under this path are considered.\n */\n outputPath: string\n /**\n * Pool of generated files to scan for indexable sources.\n */\n files: ReadonlyArray<FileNode>\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n *\n * @default false\n */\n nested?: boolean\n /**\n * Also generate a barrel for each sub-directory when nested is false.\n * No effect when nested is true (always generates hierarchical structure).\n *\n * @default false\n */\n recursive?: boolean\n}\n\n/**\n * Generates barrel `FileNode`s for the directory rooted at `outputPath`.\n */\nexport function getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Array<FileNode> {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n if (paths.length === 0) return []\n\n const tree = buildTree(outputPath, paths)\n const result: Array<FileNode> = []\n\n // Use nested walk for hierarchical barrel structure\n if (nested) {\n walkNested(tree, result)\n return result\n }\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return result\n\n walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, true, result)\n return result\n}\n\n/**\n * Builds a POSIX-normalized prefix for a plugin's output directory, with a trailing `/`.\n *\n * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.\n */\nexport function getPluginOutputPrefix(plugin: NormalizedPlugin, config: Config): string {\n return `${toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))}/`\n}\n\n/**\n * Returns `true` when `filePath` lives under any of the given excluded directory prefixes.\n *\n * Both sides are POSIX-normalized so Windows backslash paths match correctly.\n */\nexport function isExcludedPath(filePath: string, prefixes: ReadonlySet<string>): boolean {\n const normalized = toPosixPath(filePath)\n return prefixes.values().some((prefix) => normalized.startsWith(prefix))\n}\n","import path from 'node:path'\nimport { resolve } from 'node:path'\nimport { defineMiddleware } from '@kubb/core'\nimport type { Middleware } from '@kubb/core'\nimport type { BarrelConfig, PluginBarrelConfig } from './types.ts'\nimport { getBarrelFiles, getPluginOutputPrefix, isExcludedPath } from './utils.ts'\n\ndeclare global {\n namespace Kubb {\n interface PluginOptionsRegistry {\n output: {\n /**\n * Barrel configuration for this plugin's output.\n * Set to `false` to disable barrel generation for this plugin entirely; doing so also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default { type: 'named' }\n */\n barrel?: PluginBarrelConfig | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Barrel configuration for the root barrel file at `config.output.path/index.ts`.\n * Set to `false` to disable root barrel generation. Individual plugins can override\n * this via their own `output.barrel`.\n *\n * @default { type: 'named' }\n */\n barrel?: BarrelConfig | false\n }\n }\n }\n}\n\n/**\n * Generates `index.ts` barrel files for each plugin and a root barrel at `config.output.path/index.ts`.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (defaults to `{ type: 'named' }`).\n * Set `barrel: false` on a plugin to disable its barrel and exclude it from the root barrel.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@kubb/core'\n * import { middlewareBarrel } from '@kubb/middleware-barrel'\n *\n * export default defineConfig({\n * output: { path: 'src/gen', barrel: { type: 'named' } },\n * plugins: [\n * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),\n * pluginZod({ output: { path: 'schemas' } }),\n * ],\n * middleware: [middlewareBarrel()],\n * })\n * ```\n */\n\n/**\n * Stable string identifier for the barrel middleware.\n */\nexport const middlewareBarrelName = 'middleware-barrel' satisfies Middleware['name']\n\nexport const middlewareBarrel = defineMiddleware(() => {\n const excludedPrefixes = new Set<string>()\n\n return {\n name: middlewareBarrelName,\n hooks: {\n 'kubb:plugin:end'({ plugin, config, files, upsertFile }) {\n const pluginBarrel = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\n const defaultBarrel = { type: 'named' } as const\n\n let barrelConfig: PluginBarrelConfig | false\n if (pluginBarrel !== undefined) {\n barrelConfig = pluginBarrel\n } else if (configBarrel !== undefined) {\n // Root config barrel doesn't have nested, so we add it\n barrelConfig = configBarrel === false ? false : { ...configBarrel, nested: false }\n } else {\n barrelConfig = defaultBarrel\n }\n\n if (barrelConfig === false) {\n excludedPrefixes.add(getPluginOutputPrefix(plugin, config))\n return\n }\n\n const barrelType = barrelConfig.type\n const nested = barrelConfig.nested ?? false\n\n const base = resolve(config.root, config.output.path)\n const target = resolve(base, plugin.options.output.path)\n const relative = path.relative(base, target)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('Invalid output path')\n }\n const barrelFiles = getBarrelFiles({\n outputPath: target,\n files,\n barrelType,\n nested,\n recursive: true,\n })\n\n if (barrelFiles.length > 0) {\n upsertFile(...barrelFiles)\n }\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const barrelConfig = config.output.barrel ?? { type: 'named' }\n\n const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n if (barrelConfig === false) return\n\n const barrelType = barrelConfig.type\n\n const rootBarrelFiles = getBarrelFiles({\n outputPath: resolve(config.root, config.output.path),\n files: filteredFiles,\n barrelType,\n })\n\n if (rootBarrelFiles.length > 0) {\n upsertFile(...rootBarrelFiles)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
package/extension.yaml ADDED
@@ -0,0 +1,191 @@
1
+ $schema: https://kubb.dev/schemas/extension.json
2
+ kind: middleware
3
+ id: middleware-barrel
4
+ name: Barrel
5
+ description: Automatically generates barrel files (index.ts exports) for every plugin output directory. Ships with Kubb and is enabled by default.
6
+ category: output
7
+ type: official
8
+ npmPackage: '@kubb/middleware-barrel'
9
+ docsPath: /middlewares/middleware-barrel
10
+ repo: https://github.com/kubb-labs/kubb
11
+ maintainers:
12
+ - name: Stijn Van Hulle
13
+ github: stijnvanhulle
14
+ compatibility:
15
+ kubb: '>=5.0.0'
16
+ node: '>=22'
17
+ tags:
18
+ - barrel
19
+ - index
20
+ - exports
21
+ - output
22
+ resources:
23
+ documentation: https://kubb.dev/middlewares/middleware-barrel
24
+ repository: https://github.com/kubb-labs/kubb
25
+ issues: https://github.com/kubb-labs/kubb/issues
26
+ changelog: https://github.com/kubb-labs/kubb/blob/main/packages/middleware-barrel/CHANGELOG.md
27
+ featured: true
28
+ intro: |-
29
+ The barrel middleware automatically generates `index.ts` (and `index.js`) re-export files for every plugin output directory **and** a root barrel at `config.output.path/index.ts` after the build finishes.
30
+
31
+ It ships with Kubb and is registered as a default middleware in `defineConfig`, so you get barrel files out of the box with no extra configuration. When `middlewareBarrel` is part of `config.middleware`, `defineConfig` also applies a default `output.barrel` of `{ type: 'named' }`. Custom middleware lists that omit `middlewareBarrel` leave `barrel` untouched.
32
+
33
+ Plugins inherit `output.barrel` from `config.output.barrel` when their own value is omitted. Setting `barrel: false` on a plugin disables that plugin's barrel **and** excludes its files from the root barrel.
34
+ options:
35
+ - name: barrel
36
+ type: "{ type: 'all' | 'named', nested?: boolean } | false"
37
+ required: false
38
+ default: "{ type: 'named' }"
39
+ description: |-
40
+ Re-export style for the generated barrel files. Configured via `output.barrel` in `defineConfig` (root level) or per plugin via the plugin's own `output.barrel`. This is not a middleware argument.
41
+
42
+ At the config level:
43
+ - `{ type: 'all' }` — `export * from '...'` for every generated file
44
+ - `{ type: 'named' }` — `export { name1, name2 } from '...'` using each file's named exports
45
+ - `false` — disables barrel generation entirely
46
+
47
+ At the plugin level, the `nested` flag is also available:
48
+ - `{ type: 'named', nested: true }` — generates a barrel for every directory, re-exporting only direct children, creating a chain for imports from any depth
49
+ - `false` — disables barrel generation for that plugin and excludes its files from the root barrel
50
+
51
+ The `{ type: 'named' }` default is applied automatically by `defineConfig` only when `middlewareBarrel` is part of `config.middleware`.
52
+ codeBlock:
53
+ lang: typescript
54
+ title: kubb.config.ts
55
+ twoslash: false
56
+ code: |-
57
+ import { defineConfig } from 'kubb'
58
+ import { middlewareBarrel } from '@kubb/middleware-barrel'
59
+
60
+ export default defineConfig({
61
+ input: { path: './petstore.yaml' },
62
+ output: { path: './src/gen', barrel: { type: 'named' } },
63
+ middleware: [middlewareBarrel()],
64
+ })
65
+ examples:
66
+ - name: "barrel: { type: 'named' } (default)"
67
+ files:
68
+ - name: kubb.config.ts
69
+ lang: typescript
70
+ twoslash: false
71
+ code: |-
72
+ import { defineConfig } from 'kubb'
73
+
74
+ export default defineConfig({
75
+ input: { path: './petstore.yaml' },
76
+ output: { path: './src/gen' },
77
+ })
78
+ - name: 'Generated output'
79
+ lang: typescript
80
+ twoslash: false
81
+ code: |-
82
+ // src/gen/index.ts
83
+ export { getUser, User } from './api/user'
84
+ export { getPost, Post } from './api/post'
85
+ export { User } from './api/types/User'
86
+ export { useUser } from './hooks/useUser'
87
+
88
+ // src/gen/api/index.ts
89
+ export { getUser, User } from './user'
90
+ export { getPost, Post } from './post'
91
+ export { User } from './types/User'
92
+
93
+ // src/gen/api/types/index.ts
94
+ export { User } from './User'
95
+ - name: "barrel: { type: 'all' }"
96
+ files:
97
+ - name: kubb.config.ts
98
+ lang: typescript
99
+ twoslash: false
100
+ code: |-
101
+ import { defineConfig } from 'kubb'
102
+
103
+ export default defineConfig({
104
+ input: { path: './petstore.yaml' },
105
+ output: { path: './src/gen', barrel: { type: 'all' } },
106
+ })
107
+ - name: 'Generated output'
108
+ lang: typescript
109
+ twoslash: false
110
+ code: |-
111
+ // src/gen/index.ts
112
+ export * from './api/user'
113
+ export * from './api/post'
114
+ export * from './api/types/User'
115
+ export * from './hooks/useUser'
116
+
117
+ // src/gen/api/index.ts
118
+ export * from './user'
119
+ export * from './post'
120
+ export * from './types/User'
121
+
122
+ // src/gen/api/types/index.ts
123
+ export * from './User'
124
+ - name: "barrel: { type: 'named', nested: true }"
125
+ files:
126
+ - name: kubb.config.ts
127
+ lang: typescript
128
+ twoslash: false
129
+ code: |-
130
+ import { defineConfig } from 'kubb'
131
+
132
+ export default defineConfig({
133
+ input: { path: './petstore.yaml' },
134
+ output: { path: './src/gen', barrel: { type: 'named', nested: true } },
135
+ })
136
+ - name: 'Generated output (chained structure)'
137
+ lang: typescript
138
+ twoslash: false
139
+ code: |-
140
+ // src/gen/index.ts (only exports directories)
141
+ export * from './api'
142
+ export * from './hooks'
143
+
144
+ // src/gen/api/index.ts (exports files and subdirs)
145
+ export * from './user'
146
+ export * from './post'
147
+ export * from './types'
148
+
149
+ // src/gen/api/types/index.ts (exports files)
150
+ export * from './User'
151
+ - name: Disable barrel for a single plugin
152
+ files:
153
+ - name: kubb.config.ts
154
+ lang: typescript
155
+ twoslash: false
156
+ code: |-
157
+ import { defineConfig } from 'kubb'
158
+ import { pluginTs } from '@kubb/plugin-ts'
159
+ import { pluginZod } from '@kubb/plugin-zod'
160
+
161
+ // pluginZod opts out: no zod/index.ts is created
162
+ // and zod files are excluded from the root index.ts.
163
+ export default defineConfig({
164
+ input: { path: './petstore.yaml' },
165
+ output: { path: './src/gen' },
166
+ plugins: [
167
+ pluginTs(),
168
+ pluginZod({ output: { barrel: false } }),
169
+ ],
170
+ })
171
+ - name: Disable the root barrel only
172
+ files:
173
+ - name: kubb.config.ts
174
+ lang: typescript
175
+ twoslash: false
176
+ code: |-
177
+ import { defineConfig } from 'kubb'
178
+
179
+ // No root index.ts is generated, but each plugin
180
+ // still gets its own barrel using its inherited barrel config.
181
+ export default defineConfig({
182
+ input: { path: './petstore.yaml' },
183
+ output: { path: './src/gen', barrel: false },
184
+ })
185
+ notes:
186
+ - type: tip
187
+ body: |-
188
+ `middleware-barrel` is bundled with Kubb and enabled automatically when no `middleware` option is provided. You only need to install it explicitly when customising the barrel behaviour alongside other middlewares.
189
+ - type: note
190
+ body: |-
191
+ `output.barrel` is only auto-defaulted to `{ type: 'named' }` when `middlewareBarrel` is part of `config.middleware`. If you provide a custom middleware list without it, set `output.barrel` yourself if you still want a barrel.
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "@kubb/middleware-barrel",
3
- "version": "5.0.0-beta.75",
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.",
3
+ "version": "5.0.0-beta.8",
4
+ "description": "Barrel-file middleware for Kubb. Automatically generates index.ts re-export files per plugin output directory and an optional root barrel after all plugins complete.",
5
5
  "keywords": [
6
6
  "barrel",
7
- "code-generator",
8
7
  "codegen",
9
8
  "kubb",
10
9
  "middleware",
11
- "openapi",
12
10
  "typescript"
13
11
  ],
14
12
  "license": "MIT",
@@ -21,6 +19,7 @@
21
19
  "files": [
22
20
  "src",
23
21
  "dist",
22
+ "extension.yaml",
24
23
  "*.d.ts",
25
24
  "*.d.cts",
26
25
  "!/**/**.test.**",
@@ -43,11 +42,14 @@
43
42
  "access": "public",
44
43
  "registry": "https://registry.npmjs.org/"
45
44
  },
45
+ "dependencies": {
46
+ "@kubb/ast": "5.0.0-beta.8"
47
+ },
46
48
  "devDependencies": {
47
49
  "@internals/utils": "0.0.0"
48
50
  },
49
51
  "peerDependencies": {
50
- "@kubb/core": "5.0.0-beta.75"
52
+ "@kubb/core": "5.0.0-beta.8"
51
53
  },
52
54
  "engines": {
53
55
  "node": ">=22"
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { middlewareBarrel, middlewareBarrelName } from './middleware.ts'
2
- export type { BarrelType, RootBarrelType } from './types.ts'
2
+ export type { BarrelType, BarrelConfig, PluginBarrelConfig } from './types.ts'
package/src/middleware.ts CHANGED
@@ -2,38 +2,35 @@ import path from 'node:path'
2
2
  import { resolve } from 'node:path'
3
3
  import { defineMiddleware } from '@kubb/core'
4
4
  import type { Middleware } from '@kubb/core'
5
- import type { BarrelType, RootBarrelType } from './types.ts'
6
- import { getPluginOutputPrefix, isExcludedPath } from './utils/excludedPaths.ts'
7
- import { getBarrelFiles } from './utils/getBarrelFiles.ts'
5
+ import type { BarrelConfig, PluginBarrelConfig } from './types.ts'
6
+ import { getBarrelFiles, getPluginOutputPrefix, isExcludedPath } from './utils.ts'
8
7
 
9
8
  declare global {
10
9
  namespace Kubb {
11
10
  interface PluginOptionsRegistry {
12
11
  output: {
13
12
  /**
14
- * Re-export style for this plugin's barrel file.
13
+ * Barrel configuration for this plugin's output.
15
14
  * Set to `false` to disable barrel generation for this plugin entirely; doing so also
16
15
  * excludes the plugin's files from the root barrel.
17
16
  *
18
- * Falls back to `config.output.barrelType` when omitted.
17
+ * Falls back to `config.output.barrel` when omitted.
19
18
  *
20
- * @default 'named'
19
+ * @default { type: 'named' }
21
20
  */
22
- barrelType?: BarrelType | false
21
+ barrel?: PluginBarrelConfig | false
23
22
  }
24
23
  }
25
24
  interface ConfigOptionsRegistry {
26
25
  output: {
27
26
  /**
28
- * Re-export style for the root barrel file at `config.output.path/index.ts`.
27
+ * Barrel configuration for the root barrel file at `config.output.path/index.ts`.
29
28
  * Set to `false` to disable root barrel generation. Individual plugins can override
30
- * this via their own `output.barrelType`.
29
+ * this via their own `output.barrel`.
31
30
  *
32
- * `'propagate'` is not available here — it only applies at the per-plugin level.
33
- *
34
- * @default 'named'
31
+ * @default { type: 'named' }
35
32
  */
36
- barrelType?: RootBarrelType | false
33
+ barrel?: BarrelConfig | false
37
34
  }
38
35
  }
39
36
  }
@@ -42,8 +39,8 @@ declare global {
42
39
  /**
43
40
  * Generates `index.ts` barrel files for each plugin and a root barrel at `config.output.path/index.ts`.
44
41
  *
45
- * Each plugin inherits `output.barrelType` from `config.output.barrelType` (defaults to `'named'`).
46
- * Set `barrelType: false` on a plugin to disable its barrel and exclude it from the root barrel.
42
+ * Each plugin inherits `output.barrel` from `config.output.barrel` (defaults to `{ type: 'named' }`).
43
+ * Set `barrel: false` on a plugin to disable its barrel and exclude it from the root barrel.
47
44
  *
48
45
  * @example
49
46
  * ```ts
@@ -51,9 +48,9 @@ declare global {
51
48
  * import { middlewareBarrel } from '@kubb/middleware-barrel'
52
49
  *
53
50
  * export default defineConfig({
54
- * output: { path: 'src/gen', barrelType: 'named' },
51
+ * output: { path: 'src/gen', barrel: { type: 'named' } },
55
52
  * plugins: [
56
- * pluginTs({ output: { path: 'types', barrelType: 'all' } }),
53
+ * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
57
54
  * pluginZod({ output: { path: 'schemas' } }),
58
55
  * ],
59
56
  * middleware: [middlewareBarrel()],
@@ -73,13 +70,28 @@ export const middlewareBarrel = defineMiddleware(() => {
73
70
  name: middlewareBarrelName,
74
71
  hooks: {
75
72
  'kubb:plugin:end'({ plugin, config, files, upsertFile }) {
76
- const barrelType = plugin.options.output?.barrelType ?? config.output.barrelType ?? 'named'
73
+ const pluginBarrel = plugin.options.output?.barrel
74
+ const configBarrel = config.output.barrel
75
+ const defaultBarrel = { type: 'named' } as const
76
+
77
+ let barrelConfig: PluginBarrelConfig | false
78
+ if (pluginBarrel !== undefined) {
79
+ barrelConfig = pluginBarrel
80
+ } else if (configBarrel !== undefined) {
81
+ // Root config barrel doesn't have nested, so we add it
82
+ barrelConfig = configBarrel === false ? false : { ...configBarrel, nested: false }
83
+ } else {
84
+ barrelConfig = defaultBarrel
85
+ }
77
86
 
78
- if (!barrelType) {
87
+ if (barrelConfig === false) {
79
88
  excludedPrefixes.add(getPluginOutputPrefix(plugin, config))
80
89
  return
81
90
  }
82
91
 
92
+ const barrelType = barrelConfig.type
93
+ const nested = barrelConfig.nested ?? false
94
+
83
95
  const base = resolve(config.root, config.output.path)
84
96
  const target = resolve(base, plugin.options.output.path)
85
97
  const relative = path.relative(base, target)
@@ -90,6 +102,7 @@ export const middlewareBarrel = defineMiddleware(() => {
90
102
  outputPath: target,
91
103
  files,
92
104
  barrelType,
105
+ nested,
93
106
  recursive: true,
94
107
  })
95
108
 
@@ -98,17 +111,19 @@ export const middlewareBarrel = defineMiddleware(() => {
98
111
  }
99
112
  },
100
113
  'kubb:plugins:end'({ files, config, upsertFile }) {
101
- const rootBarrelType = config.output.barrelType ?? 'named'
114
+ const barrelConfig = config.output.barrel ?? { type: 'named' }
102
115
 
103
116
  const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))
104
117
  excludedPrefixes.clear()
105
118
 
106
- if (!rootBarrelType) return
119
+ if (barrelConfig === false) return
120
+
121
+ const barrelType = barrelConfig.type
107
122
 
108
123
  const rootBarrelFiles = getBarrelFiles({
109
124
  outputPath: resolve(config.root, config.output.path),
110
125
  files: filteredFiles,
111
- barrelType: rootBarrelType,
126
+ barrelType,
112
127
  })
113
128
 
114
129
  if (rootBarrelFiles.length > 0) {