@kubb/plugin-barrel 5.14.6 → 5.15.1

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/README.md CHANGED
@@ -58,11 +58,14 @@ export default defineConfig({
58
58
 
59
59
  `pluginBarrel()` takes no arguments. You configure it through `output.barrel`, on `defineConfig` or on a single plugin.
60
60
 
61
- | Option | Type | Default | Description |
62
- | ---------------------- | ------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------- |
63
- | `output.barrel` | `{ type: 'all' \| 'named', nested?: boolean } \| false` | `false` on `defineConfig` | Barrel settings. `false` on a plugin turns its barrel off and leaves its files out of the root one |
64
- | `output.barrel.type` | `'all' \| 'named'` | None | Export style for the generated barrel files |
65
- | `output.barrel.nested` | `boolean` | `false` | Plugin only. Each barrel re-exports its own directory and the barrels below it |
61
+ | Option | Type | Default | Description |
62
+ | ------------------------- | ---------------------------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------- |
63
+ | `output.barrel` | `{ type: 'all' \| 'named', nested?: boolean, namespace?: boolean } \| false` | `false` on `defineConfig` | Barrel settings. `false` on a plugin turns its barrel off and leaves its files out of the root one |
64
+ | `output.barrel.type` | `'all' \| 'named'` | None | Export style for the generated barrel files |
65
+ | `output.barrel.nested` | `boolean` | `false` | Plugin only. Each barrel re-exports its own directory and the barrels below it |
66
+ | `output.barrel.namespace` | `boolean` | `false` | Plugin only. Add named namespaces for immediate subdirectories and enable nested barrels |
67
+
68
+ Set `output.barrel.namespace: true` on a grouped fetch plugin to import a named service such as `PetService`, alongside standalone functions. Leave `sdk` unset when using query plugins. See the [fetch + React Query example](https://kubb.dev/extensions/kubb-labs/plugin-barrel/options#namespace).
66
69
 
67
70
  ### Export types
68
71
 
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
2
+ // \0rolldown/runtime.js
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -20,12 +20,48 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
20
20
  value: mod,
21
21
  enumerable: true
22
22
  }) : target, mod));
23
- //#endregion
24
23
  let node_path = require("node:path");
25
24
  node_path = __toESM(node_path, 1);
26
25
  let _kubb_core = require("@kubb/core");
27
26
  let _kubb_kit = require("@kubb/kit");
28
- //#region ../../internals/utils/src/fs.ts
27
+ // ../../internals/utils/src/casing.ts
28
+ /**
29
+ * Shared implementation for camelCase and PascalCase conversion.
30
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
31
+ * and capitalizes each word according to `pascal`.
32
+ *
33
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
34
+ */
35
+ function convert(text, pascal) {
36
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
37
+ if (word.length > 1 && word === word.toUpperCase()) return word;
38
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
39
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
40
+ }
41
+ const cache = /* @__PURE__ */ new Map();
42
+ const cacheLimit = 1e5;
43
+ function toCamelOrPascal(text, pascal) {
44
+ const key = `${pascal ? "P" : "c"}${text}`;
45
+ const cached = cache.get(key);
46
+ if (cached !== void 0) return cached;
47
+ const result = convert(text, pascal);
48
+ if (cache.size >= cacheLimit) cache.clear();
49
+ cache.set(key, result);
50
+ return result;
51
+ }
52
+ /**
53
+ * Converts `text` to PascalCase.
54
+ *
55
+ * @example Word boundaries
56
+ * `pascalCase('hello-world') // 'HelloWorld'`
57
+ *
58
+ * @example With a suffix
59
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
60
+ */
61
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
62
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
63
+ }
64
+ // ../../internals/utils/src/fs.ts
29
65
  /**
30
66
  * Converts a filesystem path to use POSIX (`/`) separators.
31
67
  *
@@ -64,17 +100,14 @@ function isPathInside(path, parent) {
64
100
  const rel = (0, node_path.relative)(resolvedParent, resolvedPath);
65
101
  return rel !== "" && !rel.startsWith("..") && !(0, node_path.isAbsolute)(rel);
66
102
  }
67
- //#endregion
68
- //#region kubb-extension.json
103
+ // kubb-extension.json
69
104
  var compatibility = {
70
105
  "kubb": ">=5.8.3",
71
106
  "node": ">=22.22"
72
107
  };
73
- //#endregion
74
- //#region package.json
75
- var version = "5.14.6";
76
- //#endregion
77
- //#region src/utils.ts
108
+ // package.json
109
+ var version = "5.15.1";
110
+ // src/utils.ts
78
111
  const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
79
112
  ".ts",
80
113
  ".tsx",
@@ -89,14 +122,30 @@ function sortedChildren(node) {
89
122
  function toRelativeModulePath(fromDir, filePath) {
90
123
  return `./${node_path.posix.relative(fromDir, filePath)}`;
91
124
  }
125
+ function getExportNames({ item, dirPath, ctx }) {
126
+ if (item.name != null) return [item.name].flat();
127
+ const itemPath = node_path.posix.join(dirPath, item.path);
128
+ const barrel = ctx.barrelFiles.get(itemPath);
129
+ if (barrel) return barrel.exports.flatMap((item) => getExportNames({
130
+ item,
131
+ dirPath: node_path.posix.dirname(barrel.path),
132
+ ctx
133
+ }));
134
+ return ctx.sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? [];
135
+ }
92
136
  /**
93
137
  * Builds the `index.ts` for `dirPath`. Keeps the first export of each name and reports later duplicates once per build.
94
138
  */
95
- function makeBarrel(dirPath, exports, { sourceFiles, reportedCollisions }) {
139
+ function makeBarrel(dirPath, exports, ctx) {
140
+ const { reportedCollisions } = ctx;
96
141
  const owners = /* @__PURE__ */ new Map();
97
142
  const uniqueExports = exports.flatMap((item) => {
98
143
  const itemPath = node_path.posix.join(dirPath, item.path);
99
- const uniqueNames = (item.name == null ? sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? [] : [item.name].flat()).filter((name) => {
144
+ const uniqueNames = getExportNames({
145
+ item,
146
+ dirPath,
147
+ ctx
148
+ }).filter((name) => {
100
149
  const first = owners.get(name);
101
150
  if (!first) {
102
151
  owners.set(name, item.path);
@@ -124,11 +173,13 @@ function makeBarrel(dirPath, exports, { sourceFiles, reportedCollisions }) {
124
173
  name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0]
125
174
  })] : [];
126
175
  });
127
- return _kubb_kit.ast.factory.createFile({
176
+ const file = _kubb_kit.ast.factory.createFile({
128
177
  baseName: "index.ts",
129
178
  path: `${dirPath}${BARREL_SUFFIX}`,
130
179
  exports: uniqueExports
131
180
  });
181
+ ctx.barrelFiles.set(file.path, file);
182
+ return file;
132
183
  }
133
184
  function indexableNames(sources, isTypeOnly) {
134
185
  const names = sources.flatMap((source) => source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []);
@@ -169,6 +220,7 @@ function walkFlat(node, ctx, isRoot = true) {
169
220
  const barrels = results.flatMap((result) => result.barrels);
170
221
  const leaves = results.flatMap((result) => result.leaves);
171
222
  const exports = isRoot || ctx.recursive ? leaves.flatMap((leaf) => ctx.leafExports(node.path, leaf)) : [];
223
+ if (isRoot) exports.push(...ctx.additionalExports);
172
224
  return {
173
225
  barrels: exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels,
174
226
  leaves
@@ -187,10 +239,25 @@ function walkNested(node, ctx) {
187
239
  barrels: [],
188
240
  exports: ctx.leafExports(node.path, child.path)
189
241
  };
190
- const barrels = walkNested(child, ctx);
242
+ const barrels = walkNested(child, {
243
+ ...ctx,
244
+ namespace: false
245
+ });
246
+ if (barrels.length === 0) return {
247
+ barrels,
248
+ exports: []
249
+ };
250
+ const modulePath = toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`);
251
+ const exports = [_kubb_kit.ast.factory.createExport({ path: modulePath })];
252
+ const name = pascalCase(node_path.posix.basename(child.path));
253
+ if (ctx.namespace) exports.push(_kubb_kit.ast.factory.createExport({
254
+ path: modulePath,
255
+ name: /^[A-Za-z]/.test(name) ? name : `_${name}`,
256
+ asAlias: true
257
+ }));
191
258
  return {
192
259
  barrels,
193
- exports: barrels.length > 0 ? [_kubb_kit.ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) })] : []
260
+ exports
194
261
  };
195
262
  });
196
263
  const barrels = results.flatMap((result) => result.barrels);
@@ -249,20 +316,22 @@ function findDirectory(tree, targetPath) {
249
316
  * getBarrelFiles({ index, targetPath, barrelType }).forEach((file) => upsertFile(file))
250
317
  * ```
251
318
  */
252
- function getBarrelFiles({ index, targetPath, barrelType, nested = false, recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
319
+ function getBarrelFiles({ index, targetPath, barrelType, nested = false, namespace = false, additionalExports = [], recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
253
320
  const node = targetPath ? findDirectory(index.tree, toPosixPath(targetPath)) : index.tree;
254
321
  if (!node) return [];
255
322
  const strategy = barrelType === "named" ? namedStrategy : allStrategy;
256
323
  const ctx = {
257
324
  sourceFiles: index.sourceFiles,
325
+ barrelFiles: /* @__PURE__ */ new Map(),
258
326
  reportedCollisions,
259
327
  recursive,
328
+ namespace,
329
+ additionalExports,
260
330
  leafExports: (dirPath, leafPath) => strategy(toRelativeModulePath(dirPath, leafPath), index.sourceFiles.get(leafPath))
261
331
  };
262
- return nested ? walkNested(node, ctx) : walkFlat(node, ctx).barrels;
332
+ return nested || namespace ? walkNested(node, ctx) : walkFlat(node, ctx).barrels;
263
333
  }
264
- //#endregion
265
- //#region src/plugin.ts
334
+ // src/plugin.ts
266
335
  /**
267
336
  * Applies a plugin's configured `output.banner`/`footer` to a barrel file, flagged as `isBarrel`.
268
337
  *
@@ -360,7 +429,15 @@ const pluginBarrel = (0, _kubb_core.defineExtension)({
360
429
  if (target !== base && isPathInside(target, base)) excludedTargets.push(target);
361
430
  return;
362
431
  }
363
- if (target === base) return;
432
+ if (target === base) {
433
+ if (barrelConfig.namespace) _kubb_kit.Diagnostics.report({
434
+ code: _kubb_kit.Diagnostics.code.pluginWarning,
435
+ severity: "warning",
436
+ message: `Plugin "${plugin.name}" sets \`output.barrel.namespace\` but writes to the output root, so no namespaces are generated.`,
437
+ help: "Give the plugin its own `output.path`, such as `client`."
438
+ });
439
+ return;
440
+ }
364
441
  if (plugin.options.output.mode === "file") return;
365
442
  if (!isPathInside(target, base)) throw new _kubb_kit.Diagnostics.Error({
366
443
  code: _kubb_kit.Diagnostics.code.pathTraversal,
@@ -373,7 +450,8 @@ const pluginBarrel = (0, _kubb_core.defineExtension)({
373
450
  plugin,
374
451
  target,
375
452
  barrelType: barrelConfig.type,
376
- nested: barrelConfig.nested ?? false
453
+ nested: barrelConfig.nested ?? false,
454
+ namespace: barrelConfig.namespace ?? false
377
455
  });
378
456
  },
379
457
  "kubb:plugins:end"({ files, config, upsertFile }) {
@@ -389,12 +467,14 @@ const pluginBarrel = (0, _kubb_core.defineExtension)({
389
467
  excludedTargets.length = 0;
390
468
  const index = buildBarrelIndex(outputPath, relevantFiles);
391
469
  const reportedCollisions = /* @__PURE__ */ new Set();
392
- pendingBarrels.forEach(({ plugin, target, barrelType, nested }) => {
470
+ const namespaceExports = /* @__PURE__ */ new Map();
471
+ pendingBarrels.forEach(({ plugin, target, barrelType, nested, namespace }) => {
393
472
  getBarrelFiles({
394
473
  index,
395
474
  targetPath: target,
396
475
  barrelType,
397
476
  nested,
477
+ namespace,
398
478
  recursive: true,
399
479
  reportedCollisions
400
480
  }).forEach((file) => {
@@ -403,6 +483,14 @@ const pluginBarrel = (0, _kubb_core.defineExtension)({
403
483
  plugin,
404
484
  config
405
485
  }));
486
+ if (file.path !== `${toPosixPath(target)}/index.ts`) return;
487
+ file.exports.filter((item) => item.asAlias).forEach((item) => {
488
+ const itemPath = `./${toPosixPath(node_path.default.relative(outputPath, node_path.default.resolve(target, item.path)))}`;
489
+ namespaceExports.set(`${itemPath}\0${item.name}`, _kubb_kit.ast.factory.createExport({
490
+ ...item,
491
+ path: itemPath
492
+ }));
493
+ });
406
494
  });
407
495
  });
408
496
  pendingBarrels.length = 0;
@@ -410,6 +498,7 @@ const pluginBarrel = (0, _kubb_core.defineExtension)({
410
498
  getBarrelFiles({
411
499
  index,
412
500
  barrelType: rootBarrelConfig.type,
501
+ additionalExports: [...namespaceExports.values()],
413
502
  reportedCollisions
414
503
  }).forEach((file) => {
415
504
  upsertFile(file);
@@ -417,7 +506,6 @@ const pluginBarrel = (0, _kubb_core.defineExtension)({
417
506
  }
418
507
  }
419
508
  });
420
- //#endregion
421
509
  exports.pluginBarrel = pluginBarrel;
422
510
  exports.pluginBarrelName = pluginBarrelName;
423
511
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["resolve","relative","isAbsolute","posix","Diagnostics","ast","extname","defineExtension","path","Diagnostics"],"sources":["../../../internals/utils/src/fs.ts","../kubb-extension.json","../package.json","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\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\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","","","import { extname, posix } from 'node:path'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport { 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\n/**\n * A file (no `children`) or directory in the tree barrels are computed from. `path` is absolute and\n * POSIX-normalized, and a directory's `children` are keyed by entry name.\n */\ntype TreeNode = { path: string; children?: undefined } | DirectoryNode\ntype DirectoryNode = { path: string; children: Map<string, TreeNode> }\n\n/** A directory's children in name order, the way a directory listing sorts them. */\nfunction sortedChildren(node: DirectoryNode): Array<TreeNode> {\n return [...node.children.entries()].sort(([a], [b]) => (a < b ? -1 : 1)).map(([, child]) => child)\n}\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\ntype WalkContext = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n reportedCollisions: Set<string>\n recursive: boolean\n leafExports: (dirPath: string, leafPath: string) => Array<ast.ExportNode>\n}\n\n/**\n * Builds the `index.ts` for `dirPath`. Keeps the first export of each name and reports later duplicates once per build.\n */\nfunction makeBarrel(dirPath: string, exports: Array<ast.ExportNode>, { sourceFiles, reportedCollisions }: WalkContext): ast.FileNode {\n const owners = new Map<string, string>()\n const uniqueExports = exports.flatMap((item) => {\n const itemPath = posix.join(dirPath, item.path)\n // A wildcard export carries no names, so collisions are checked against its source file.\n const names = item.name == null ? (sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? []) : [item.name].flat()\n const uniqueNames = names.filter((name) => {\n const first = owners.get(name)\n if (!first) {\n owners.set(name, item.path)\n return true\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (!reportedCollisions.has(key)) {\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n return false\n })\n\n if (item.name == null) return [item]\n return uniqueNames.length > 0 ? [ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] })] : []\n })\n\n return ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n}\n\ntype LeafStrategy = (path: string, sourceFile: ast.FileNode | undefined) => Array<ast.ExportNode>\n\nfunction indexableNames(sources: ReadonlyArray<ast.SourceNode>, isTypeOnly: boolean): Array<string> {\n const names = sources.flatMap((source) => (source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []))\n return [...new Set(names)].sort()\n}\n\n/**\n * Re-exports the whole module, unless it has sources and none of them is indexable.\n */\nconst allStrategy: LeafStrategy = (path, sourceFile) => {\n const sources = sourceFile?.sources ?? []\n return sources.length > 0 && !sources.some((source) => source.isIndexable) ? [] : [ast.factory.createExport({ path })]\n}\n\n/**\n * Re-exports the indexable names, values and types separately, or the whole module when it has no sources.\n */\nconst namedStrategy: LeafStrategy = (path, sourceFile) => {\n if (!sourceFile?.sources.length) return [ast.factory.createExport({ path })]\n\n const valueNames = indexableNames(sourceFile.sources, false)\n const typeNames = indexableNames(sourceFile.sources, true)\n return [\n ...(valueNames.length > 0 ? [ast.factory.createExport({ name: valueNames, path })] : []),\n ...(typeNames.length > 0 ? [ast.factory.createExport({ name: typeNames, path, isTypeOnly: true })] : []),\n ]\n}\n\ntype FlatWalk = { barrels: Array<ast.FileNode>; leaves: Array<string> }\n\n/**\n * Post-order walk that builds a barrel re-exporting every leaf below the visited directory, for\n * the start node and, with `recursive`, every sub-directory. Also returns the subtree's leaf paths.\n */\nfunction walkFlat(node: DirectoryNode, ctx: WalkContext, isRoot = true): FlatWalk {\n const results = sortedChildren(node).map((child): FlatWalk => (child.children ? walkFlat(child, ctx, false) : { barrels: [], leaves: [child.path] }))\n const barrels = results.flatMap((result) => result.barrels)\n const leaves = results.flatMap((result) => result.leaves)\n const exports = isRoot || ctx.recursive ? leaves.flatMap((leaf) => ctx.leafExports(node.path, leaf)) : []\n\n return { barrels: exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels, leaves }\n}\n\n/**\n * Recursive walk that builds one barrel per directory, re-exporting files and sub-barrels.\n * Used when nested: true. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. A sub-directory that\n * produced no barrel is never re-exported.\n */\nfunction walkNested(node: DirectoryNode, ctx: WalkContext): Array<ast.FileNode> {\n const results = sortedChildren(node).map((child) => {\n if (!child.children) return { barrels: [], exports: ctx.leafExports(node.path, child.path) }\n\n // A sub-directory has barrels only when it has its own `index.ts`.\n const barrels = walkNested(child, ctx)\n const exports = barrels.length > 0 ? [ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) })] : []\n return { barrels, exports }\n })\n const barrels = results.flatMap((result) => result.barrels)\n const exports = results.flatMap((result) => result.exports)\n\n return exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels\n}\n\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: DirectoryNode\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<ast.FileNode>): BarrelIndex {\n const tree: DirectoryNode = { path: toPosixPath(outputPath), children: new Map() }\n const sourceFiles = new Map(\n files.flatMap((file) => {\n const filePath = toPosixPath(file.path)\n const isSource = filePath.startsWith(`${tree.path}/`) && !filePath.endsWith(BARREL_SUFFIX) && SOURCE_EXTENSIONS.has(extname(filePath))\n return isSource ? [[filePath, file] as const] : []\n }),\n )\n\n for (const filePath of sourceFiles.keys()) {\n const parts = filePath\n .slice(tree.path.length + 1)\n .split('/')\n .filter(Boolean)\n parts.reduce<DirectoryNode>((dir, part, i) => {\n const path = `${dir.path}/${part}`\n const child: TreeNode = dir.children.get(part) ?? (i === parts.length - 1 ? { path } : { path, children: new Map() })\n dir.children.set(part, child)\n return child.children ? child : dir\n }, tree)\n }\n\n return { tree, sourceFiles }\n}\n\n/**\n * Locates the directory node for `targetPath` within an index tree. Returns `undefined` when no\n * file exists under `targetPath` (nothing to barrel).\n */\nfunction findDirectory(tree: DirectoryNode, targetPath: string): DirectoryNode | undefined {\n if (targetPath === tree.path) return tree\n if (!targetPath.startsWith(`${tree.path}/`)) return undefined\n\n const node = targetPath\n .slice(tree.path.length + 1)\n .split('/')\n .reduce<TreeNode | undefined>((current, part) => current?.children?.get(part), tree)\n return node?.children ? node : undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /** Absolute directory to root the barrel(s) at, a subtree of the index root. Defaults to the index root. */\n targetPath?: string\n /** `'all'` re-exports whole modules (`export * from './x'`), `'named'` only the indexable named symbols. */\n barrelType: BarrelType\n /** One `index.ts` per sub-directory, each re-exporting only what is directly inside it. */\n nested?: boolean\n /** Also generate a barrel for each sub-directory when `nested` is false. */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Returns the barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * getBarrelFiles({ index, targetPath, barrelType }).forEach((file) => upsertFile(file))\n * ```\n */\nexport function getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Array<ast.FileNode> {\n const node = targetPath ? findDirectory(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return []\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n const ctx: WalkContext = {\n sourceFiles: index.sourceFiles,\n reportedCollisions,\n recursive,\n leafExports: (dirPath, leafPath) => strategy(toRelativeModulePath(dirPath, leafPath), index.sourceFiles.get(leafPath)),\n }\n\n return nested ? walkNested(node, ctx) : walkFlat(node, ctx).barrels\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport { defineExtension } from '@kubb/core'\nimport { Diagnostics } from '@kubb/kit'\nimport type { ast, Config, NormalizedPlugin, Plugin } from '@kubb/kit'\nimport { compatibility } from '../kubb-extension.json'\nimport { version } from '../package.json'\nimport type { BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles } from './utils.ts'\n\n/**\n * Applies a plugin's configured `output.banner`/`footer` to a barrel file, flagged as `isBarrel`.\n *\n * Resolves through the plugin's own resolver, and only when the plugin explicitly sets a\n * banner/footer, so barrels stay banner-free by default and never inherit the implicit\n * \"Generated by Kubb\" notice.\n */\nfunction withBarrelBannerFooter({ file, plugin, config }: { file: ast.FileNode; plugin: NormalizedPlugin; config: Config }): ast.FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!resolver || (!hasBanner && !hasFooter)) return file\n\n const context = { output, config, file: { path: file.path, baseName: file.baseName, isBarrel: true } }\n return {\n ...file,\n banner: hasBanner ? resolver.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(undefined, context) : file.footer,\n }\n}\n\n/**\n * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups\n * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n}\n\ntype BarrelState = {\n excludedPlugins: Set<string>\n // Subdirectories of `barrel: false` plugins, for files without a `meta.pluginName` owner.\n excludedTargets: Array<string>\n pendingBarrels: Array<PendingBarrel>\n}\n\n// One state per build, keyed by the build's `config`. The object form has no factory closure to hold it.\nconst states = new WeakMap<Config, BarrelState>()\n\nfunction getState(config: Config): BarrelState {\n const state = states.get(config) ?? { excludedPlugins: new Set(), excludedTargets: [], pendingBarrels: [] }\n states.set(config, state)\n return state\n}\n\n/**\n * Generates an `index.ts` for every plugin output directory and one root\n * barrel at `config.output.path/index.ts` after the build completes. Ships\n * with Kubb and is registered by default in `defineConfig`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * from the root barrel.\n *\n * A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output\n * is a single file. The root barrel re-exports that file directly.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@kubb/core'\n * import { pluginBarrel } from '@kubb/plugin-barrel'\n * import { pluginTs } from '@kubb/plugin-ts'\n * import { pluginZod } from '@kubb/plugin-zod'\n *\n * export default defineConfig({\n * input: './petStore.yaml',\n * output: { path: 'src/gen', barrel: { type: 'named' } },\n * plugins: [\n * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),\n * pluginZod({ output: { path: 'schemas' } }),\n * pluginBarrel(),\n * ],\n * })\n * ```\n */\nexport const pluginBarrel = defineExtension({\n kind: 'plugin',\n meta: { name: pluginBarrelName, version, compatibility, issues: 'https://github.com/kubb-labs/kubb/issues' },\n enforce: 'post',\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n\n // A plugin-level `false` wins over an enabled root barrel.\n const barrelConfig: PluginBarrelConfig | false = plugin.options.output?.barrel ?? config.output.barrel ?? false\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path ?? '.')\n\n if (barrelConfig === false) {\n excludedPlugins.add(plugin.name)\n // The output root holds every plugin's files, so there only the owner match applies.\n if (target !== base && isPathInside(target, base)) {\n excludedTargets.push(target)\n }\n return\n }\n\n // A plugin at the output root (no own `output.path`) gets no per-plugin barrel, which would\n // claim every other plugin's directory. The root barrel picks up its files.\n if (target === base) {\n return\n }\n\n // `mode: 'file'` writes a single file, so there is no directory to barrel. The root barrel\n // re-exports that file as a direct leaf of `config.output.path`.\n if (plugin.options.output.mode === 'file') {\n return\n }\n\n if (!isPathInside(target, base)) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.pathTraversal,\n severity: 'error',\n message: `Output path \"${plugin.options.output.path}\" of plugin \"${plugin.name}\" resolves outside the output directory \"${base}\".`,\n help: 'Keep the plugin `output.path` within `config.output.path`.',\n location: { kind: 'config' },\n })\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = files.filter((file) => {\n const owner = (file.meta as { pluginName?: string } | undefined)?.pluginName\n if (owner) return !excludedPlugins.has(owner)\n\n return !excludedTargets.some((target) => isPathInside(file.path, target))\n })\n excludedPlugins.clear()\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n pendingBarrels.forEach(({ plugin, target, barrelType, nested }) => {\n getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions }).forEach((file) => {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n })\n })\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions }).forEach((file) => {\n upsertFile(file)\n })\n },\n },\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["resolve","relative","isAbsolute","posix","Diagnostics","ast","extname","defineExtension","path","Diagnostics","ast"],"sources":["../../../internals/utils/src/casing.ts","../../../internals/utils/src/fs.ts","../kubb-extension.json","../package.json","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["type Options = {\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction convert(text: string, pascal: boolean): string {\n return text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n .split(/[\\s\\-_./\\\\:]+/)\n .filter(Boolean)\n .map((word, i) => {\n if (word.length > 1 && word === word.toUpperCase()) return word\n const head = i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()\n return head + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n// Plugins case the same names on every visit. Clearing at the limit bounds a long-running host.\nconst cache = new Map<string, string>()\nconst cacheLimit = 100_000\n\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const key = `${pascal ? 'P' : 'c'}${text}`\n const cached = cache.get(key)\n if (cached !== undefined) return cached\n\n const result = convert(text, pascal)\n if (cache.size >= cacheLimit) cache.clear()\n cache.set(key, result)\n return result\n}\n\n/**\n * Converts `text` to camelCase.\n *\n * @example Word boundaries\n * `camelCase('hello-world') // 'helloWorld'`\n *\n * @example With a prefix\n * `camelCase('tag', { prefix: 'create' }) // 'createTag'`\n */\nexport function camelCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n *\n * @example Word boundaries\n * `pascalCase('hello-world') // 'HelloWorld'`\n *\n * @example With a suffix\n * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`\n */\nexport function pascalCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\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\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","","","import { extname, posix } from 'node:path'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport { pascalCase, 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\n/**\n * A file (no `children`) or directory in the tree barrels are computed from. `path` is absolute and\n * POSIX-normalized, and a directory's `children` are keyed by entry name.\n */\ntype TreeNode = { path: string; children?: undefined } | DirectoryNode\ntype DirectoryNode = { path: string; children: Map<string, TreeNode> }\n\n/** A directory's children in name order, the way a directory listing sorts them. */\nfunction sortedChildren(node: DirectoryNode): Array<TreeNode> {\n return [...node.children.entries()].sort(([a], [b]) => (a < b ? -1 : 1)).map(([, child]) => child)\n}\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\ntype WalkContext = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n barrelFiles: Map<string, ast.FileNode>\n reportedCollisions: Set<string>\n recursive: boolean\n namespace: boolean\n additionalExports: ReadonlyArray<ast.ExportNode>\n leafExports: (dirPath: string, leafPath: string) => Array<ast.ExportNode>\n}\n\nfunction getExportNames({ item, dirPath, ctx }: { item: ast.ExportNode; dirPath: string; ctx: WalkContext }): Array<string> {\n if (item.name != null) return [item.name].flat()\n\n const itemPath = posix.join(dirPath, item.path)\n const barrel = ctx.barrelFiles.get(itemPath)\n if (barrel) return barrel.exports.flatMap((item) => getExportNames({ item, dirPath: posix.dirname(barrel.path), ctx }))\n\n return ctx.sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? []\n}\n\n/**\n * Builds the `index.ts` for `dirPath`. Keeps the first export of each name and reports later duplicates once per build.\n */\nfunction makeBarrel(dirPath: string, exports: Array<ast.ExportNode>, ctx: WalkContext): ast.FileNode {\n const { reportedCollisions } = ctx\n const owners = new Map<string, string>()\n const uniqueExports = exports.flatMap((item) => {\n const itemPath = posix.join(dirPath, item.path)\n // A wildcard export carries no names, so collisions are checked against its source file.\n const names = getExportNames({ item, dirPath, ctx })\n const uniqueNames = names.filter((name) => {\n const first = owners.get(name)\n if (!first) {\n owners.set(name, item.path)\n return true\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (!reportedCollisions.has(key)) {\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n return false\n })\n\n if (item.name == null) return [item]\n return uniqueNames.length > 0 ? [ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] })] : []\n })\n\n const file = ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n ctx.barrelFiles.set(file.path, file)\n return file\n}\n\ntype LeafStrategy = (path: string, sourceFile: ast.FileNode | undefined) => Array<ast.ExportNode>\n\nfunction indexableNames(sources: ReadonlyArray<ast.SourceNode>, isTypeOnly: boolean): Array<string> {\n const names = sources.flatMap((source) => (source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []))\n return [...new Set(names)].sort()\n}\n\n/**\n * Re-exports the whole module, unless it has sources and none of them is indexable.\n */\nconst allStrategy: LeafStrategy = (path, sourceFile) => {\n const sources = sourceFile?.sources ?? []\n return sources.length > 0 && !sources.some((source) => source.isIndexable) ? [] : [ast.factory.createExport({ path })]\n}\n\n/**\n * Re-exports the indexable names, values and types separately, or the whole module when it has no sources.\n */\nconst namedStrategy: LeafStrategy = (path, sourceFile) => {\n if (!sourceFile?.sources.length) return [ast.factory.createExport({ path })]\n\n const valueNames = indexableNames(sourceFile.sources, false)\n const typeNames = indexableNames(sourceFile.sources, true)\n return [\n ...(valueNames.length > 0 ? [ast.factory.createExport({ name: valueNames, path })] : []),\n ...(typeNames.length > 0 ? [ast.factory.createExport({ name: typeNames, path, isTypeOnly: true })] : []),\n ]\n}\n\ntype FlatWalk = { barrels: Array<ast.FileNode>; leaves: Array<string> }\n\n/**\n * Post-order walk that builds a barrel re-exporting every leaf below the visited directory, for\n * the start node and, with `recursive`, every sub-directory. Also returns the subtree's leaf paths.\n */\nfunction walkFlat(node: DirectoryNode, ctx: WalkContext, isRoot = true): FlatWalk {\n const results = sortedChildren(node).map((child): FlatWalk => (child.children ? walkFlat(child, ctx, false) : { barrels: [], leaves: [child.path] }))\n const barrels = results.flatMap((result) => result.barrels)\n const leaves = results.flatMap((result) => result.leaves)\n const exports = isRoot || ctx.recursive ? leaves.flatMap((leaf) => ctx.leafExports(node.path, leaf)) : []\n if (isRoot) exports.push(...ctx.additionalExports)\n\n return { barrels: exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels, leaves }\n}\n\n/**\n * Recursive walk that builds one barrel per directory, re-exporting files and sub-barrels.\n * Used when nested: true. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. A sub-directory that\n * produced no barrel is never re-exported.\n */\nfunction walkNested(node: DirectoryNode, ctx: WalkContext): Array<ast.FileNode> {\n const results = sortedChildren(node).map((child) => {\n if (!child.children) return { barrels: [], exports: ctx.leafExports(node.path, child.path) }\n\n // A sub-directory has barrels only when it has its own `index.ts`.\n const barrels = walkNested(child, { ...ctx, namespace: false })\n if (barrels.length === 0) return { barrels, exports: [] }\n\n const modulePath = toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`)\n const exports = [ast.factory.createExport({ path: modulePath })]\n const name = pascalCase(posix.basename(child.path))\n if (ctx.namespace) exports.push(ast.factory.createExport({ path: modulePath, name: /^[A-Za-z]/.test(name) ? name : `_${name}`, asAlias: true }))\n return { barrels, exports }\n })\n const barrels = results.flatMap((result) => result.barrels)\n const exports = results.flatMap((result) => result.exports)\n\n return exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels\n}\n\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: DirectoryNode\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<ast.FileNode>): BarrelIndex {\n const tree: DirectoryNode = { path: toPosixPath(outputPath), children: new Map() }\n const sourceFiles = new Map(\n files.flatMap((file) => {\n const filePath = toPosixPath(file.path)\n const isSource = filePath.startsWith(`${tree.path}/`) && !filePath.endsWith(BARREL_SUFFIX) && SOURCE_EXTENSIONS.has(extname(filePath))\n return isSource ? [[filePath, file] as const] : []\n }),\n )\n\n for (const filePath of sourceFiles.keys()) {\n const parts = filePath\n .slice(tree.path.length + 1)\n .split('/')\n .filter(Boolean)\n parts.reduce<DirectoryNode>((dir, part, i) => {\n const path = `${dir.path}/${part}`\n const child: TreeNode = dir.children.get(part) ?? (i === parts.length - 1 ? { path } : { path, children: new Map() })\n dir.children.set(part, child)\n return child.children ? child : dir\n }, tree)\n }\n\n return { tree, sourceFiles }\n}\n\n/**\n * Locates the directory node for `targetPath` within an index tree. Returns `undefined` when no\n * file exists under `targetPath` (nothing to barrel).\n */\nfunction findDirectory(tree: DirectoryNode, targetPath: string): DirectoryNode | undefined {\n if (targetPath === tree.path) return tree\n if (!targetPath.startsWith(`${tree.path}/`)) return undefined\n\n const node = targetPath\n .slice(tree.path.length + 1)\n .split('/')\n .reduce<TreeNode | undefined>((current, part) => current?.children?.get(part), tree)\n return node?.children ? node : undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /** Absolute directory to root the barrel(s) at, a subtree of the index root. Defaults to the index root. */\n targetPath?: string\n /** `'all'` re-exports whole modules (`export * from './x'`), `'named'` only the indexable named symbols. */\n barrelType: BarrelType\n /** One `index.ts` per sub-directory, each re-exporting only what is directly inside it. */\n nested?: boolean\n /** Add PascalCase namespace exports for immediate subdirectories and enable nested barrels. */\n namespace?: boolean\n /** Extra exports to forward from plugin barrels into the root barrel. */\n additionalExports?: ReadonlyArray<ast.ExportNode>\n /** Also generate a barrel for each sub-directory when `nested` is false. */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Returns the barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * getBarrelFiles({ index, targetPath, barrelType }).forEach((file) => upsertFile(file))\n * ```\n */\nexport function getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n namespace = false,\n additionalExports = [],\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Array<ast.FileNode> {\n const node = targetPath ? findDirectory(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return []\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n const ctx: WalkContext = {\n sourceFiles: index.sourceFiles,\n barrelFiles: new Map(),\n reportedCollisions,\n recursive,\n namespace,\n additionalExports,\n leafExports: (dirPath, leafPath) => strategy(toRelativeModulePath(dirPath, leafPath), index.sourceFiles.get(leafPath)),\n }\n\n return nested || namespace ? walkNested(node, ctx) : walkFlat(node, ctx).barrels\n}\n","import path from 'node:path'\nimport { isPathInside, toPosixPath } from '@internals/utils'\nimport { defineExtension } from '@kubb/core'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/kit'\nimport { compatibility } from '../kubb-extension.json'\nimport { version } from '../package.json'\nimport type { BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles } from './utils.ts'\n\n/**\n * Applies a plugin's configured `output.banner`/`footer` to a barrel file, flagged as `isBarrel`.\n *\n * Resolves through the plugin's own resolver, and only when the plugin explicitly sets a\n * banner/footer, so barrels stay banner-free by default and never inherit the implicit\n * \"Generated by Kubb\" notice.\n */\nfunction withBarrelBannerFooter({ file, plugin, config }: { file: ast.FileNode; plugin: NormalizedPlugin; config: Config }): ast.FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!resolver || (!hasBanner && !hasFooter)) return file\n\n const context = { output, config, file: { path: file.path, baseName: file.baseName, isBarrel: true } }\n return {\n ...file,\n banner: hasBanner ? resolver.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(undefined, context) : file.footer,\n }\n}\n\n/**\n * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups\n * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n namespace: boolean\n}\n\ntype BarrelState = {\n excludedPlugins: Set<string>\n // Subdirectories of `barrel: false` plugins, for files without a `meta.pluginName` owner.\n excludedTargets: Array<string>\n pendingBarrels: Array<PendingBarrel>\n}\n\n// One state per build, keyed by the build's `config`. The object form has no factory closure to hold it.\nconst states = new WeakMap<Config, BarrelState>()\n\nfunction getState(config: Config): BarrelState {\n const state = states.get(config) ?? { excludedPlugins: new Set(), excludedTargets: [], pendingBarrels: [] }\n states.set(config, state)\n return state\n}\n\n/**\n * Generates an `index.ts` for every plugin output directory and one root\n * barrel at `config.output.path/index.ts` after the build completes. Ships\n * with Kubb and is registered by default in `defineConfig`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * from the root barrel.\n *\n * A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output\n * is a single file. The root barrel re-exports that file directly.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@kubb/core'\n * import { pluginBarrel } from '@kubb/plugin-barrel'\n * import { pluginTs } from '@kubb/plugin-ts'\n * import { pluginZod } from '@kubb/plugin-zod'\n *\n * export default defineConfig({\n * input: './petStore.yaml',\n * output: { path: 'src/gen', barrel: { type: 'named' } },\n * plugins: [\n * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),\n * pluginZod({ output: { path: 'schemas' } }),\n * pluginBarrel(),\n * ],\n * })\n * ```\n */\nexport const pluginBarrel = defineExtension({\n kind: 'plugin',\n meta: { name: pluginBarrelName, version, compatibility, issues: 'https://github.com/kubb-labs/kubb/issues' },\n enforce: 'post',\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n\n // A plugin-level `false` wins over an enabled root barrel.\n const barrelConfig: PluginBarrelConfig | false = plugin.options.output?.barrel ?? config.output.barrel ?? false\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path ?? '.')\n\n if (barrelConfig === false) {\n excludedPlugins.add(plugin.name)\n // The output root holds every plugin's files, so there only the owner match applies.\n if (target !== base && isPathInside(target, base)) {\n excludedTargets.push(target)\n }\n return\n }\n\n // A plugin at the output root (no own `output.path`) gets no per-plugin barrel, which would\n // claim every other plugin's directory. The root barrel picks up its files.\n if (target === base) {\n if (barrelConfig.namespace) {\n Diagnostics.report({\n code: Diagnostics.code.pluginWarning,\n severity: 'warning',\n message: `Plugin \"${plugin.name}\" sets \\`output.barrel.namespace\\` but writes to the output root, so no namespaces are generated.`,\n help: 'Give the plugin its own `output.path`, such as `client`.',\n })\n }\n return\n }\n\n // `mode: 'file'` writes a single file, so there is no directory to barrel. The root barrel\n // re-exports that file as a direct leaf of `config.output.path`.\n if (plugin.options.output.mode === 'file') {\n return\n }\n\n if (!isPathInside(target, base)) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.pathTraversal,\n severity: 'error',\n message: `Output path \"${plugin.options.output.path}\" of plugin \"${plugin.name}\" resolves outside the output directory \"${base}\".`,\n help: 'Keep the plugin `output.path` within `config.output.path`.',\n location: { kind: 'config' },\n })\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false, namespace: barrelConfig.namespace ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = files.filter((file) => {\n const owner = (file.meta as { pluginName?: string } | undefined)?.pluginName\n if (owner) return !excludedPlugins.has(owner)\n\n return !excludedTargets.some((target) => isPathInside(file.path, target))\n })\n excludedPlugins.clear()\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n // Keyed by path and name: plugins sharing an `output.path` produce the same aliases.\n const namespaceExports = new Map<string, ast.ExportNode>()\n\n pendingBarrels.forEach(({ plugin, target, barrelType, nested, namespace }) => {\n getBarrelFiles({ index, targetPath: target, barrelType, nested, namespace, recursive: true, reportedCollisions }).forEach((file) => {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n if (file.path !== `${toPosixPath(target)}/index.ts`) return\n file.exports\n .filter((item) => item.asAlias)\n .forEach((item) => {\n const itemPath = `./${toPosixPath(path.relative(outputPath, path.resolve(target, item.path)))}`\n namespaceExports.set(`${itemPath}\\0${item.name}`, ast.factory.createExport({ ...item, path: itemPath }))\n })\n })\n })\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n getBarrelFiles({ index, barrelType: rootBarrelConfig.type, additionalExports: [...namespaceExports.values()], reportedCollisions }).forEach((file) => {\n upsertFile(file)\n })\n },\n },\n})\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/plugin.d.ts
2
1
  /**
3
2
  * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups
4
3
  * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.
@@ -38,8 +37,6 @@ export declare const pluginBarrelName = "plugin-barrel";
38
37
  * ```
39
38
  */
40
39
  export declare const pluginBarrel: import("@kubb/core").PluginFactory<import("@kubb/core").PluginType>;
41
- //#endregion
42
- //#region src/types.d.ts
43
40
  /**
44
41
  * Barrel export strategy.
45
42
  *
@@ -56,6 +53,7 @@ type BarrelType = 'all' | 'named';
56
53
  * barrel: { type: 'named' } // single barrel with named exports
57
54
  * barrel: { type: 'all', nested: true } // hierarchical barrels with wildcard exports
58
55
  * barrel: { type: 'named', nested: true } // hierarchical barrels with named exports
56
+ * barrel: { type: 'named', namespace: true } // also export subdirectories as named namespaces
59
57
  * barrel: false // no barrel generated (default)
60
58
  * ```
61
59
  */
@@ -71,6 +69,19 @@ type PluginBarrelConfig = {
71
69
  * Creates a hierarchical barrel structure instead of flat exports from the root.
72
70
  */
73
71
  nested?: boolean;
72
+ /**
73
+ * Add a named namespace export for each immediate subdirectory, alongside its standalone exports.
74
+ * Enables nested barrels. Names use the PascalCase directory name, prefixed with `_`
75
+ * when empty or starting with a digit. Use `group.name` to name services such as `PetService`.
76
+ * Needs its own `output.path`: a plugin writing to the output root gets a warning and no namespaces.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * barrel: { type: 'named', namespace: true }
81
+ * // export * as PetService from './petService/index.ts'
82
+ * ```
83
+ */
84
+ namespace?: boolean;
74
85
  };
75
86
  /**
76
87
  * Barrel configuration at the root config level: the export strategy only, since the root barrel is never nested.
@@ -107,6 +118,5 @@ declare global {
107
118
  }
108
119
  }
109
120
  }
110
- //#endregion
111
121
  export type { BarrelConfig, BarrelType, PluginBarrelConfig };
112
122
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/plugin.d.ts
2
1
  /**
3
2
  * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups
4
3
  * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.
@@ -38,8 +37,6 @@ export declare const pluginBarrelName = "plugin-barrel";
38
37
  * ```
39
38
  */
40
39
  export declare const pluginBarrel: import("@kubb/core").PluginFactory<import("@kubb/core").PluginType>;
41
- //#endregion
42
- //#region src/types.d.ts
43
40
  /**
44
41
  * Barrel export strategy.
45
42
  *
@@ -56,6 +53,7 @@ type BarrelType = 'all' | 'named';
56
53
  * barrel: { type: 'named' } // single barrel with named exports
57
54
  * barrel: { type: 'all', nested: true } // hierarchical barrels with wildcard exports
58
55
  * barrel: { type: 'named', nested: true } // hierarchical barrels with named exports
56
+ * barrel: { type: 'named', namespace: true } // also export subdirectories as named namespaces
59
57
  * barrel: false // no barrel generated (default)
60
58
  * ```
61
59
  */
@@ -71,6 +69,19 @@ type PluginBarrelConfig = {
71
69
  * Creates a hierarchical barrel structure instead of flat exports from the root.
72
70
  */
73
71
  nested?: boolean;
72
+ /**
73
+ * Add a named namespace export for each immediate subdirectory, alongside its standalone exports.
74
+ * Enables nested barrels. Names use the PascalCase directory name, prefixed with `_`
75
+ * when empty or starting with a digit. Use `group.name` to name services such as `PetService`.
76
+ * Needs its own `output.path`: a plugin writing to the output root gets a warning and no namespaces.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * barrel: { type: 'named', namespace: true }
81
+ * // export * as PetService from './petService/index.ts'
82
+ * ```
83
+ */
84
+ namespace?: boolean;
74
85
  };
75
86
  /**
76
87
  * Barrel configuration at the root config level: the export strategy only, since the root barrel is never nested.
@@ -107,6 +118,5 @@ declare global {
107
118
  }
108
119
  }
109
120
  }
110
- //#endregion
111
121
  export type { BarrelConfig, BarrelType, PluginBarrelConfig };
112
122
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,8 +1,44 @@
1
1
  import path, { extname, isAbsolute, posix, relative, resolve } from "node:path";
2
2
  import { defineExtension } from "@kubb/core";
3
3
  import { Diagnostics, ast } from "@kubb/kit";
4
- //#endregion
5
- //#region ../../internals/utils/src/fs.ts
4
+ // ../../internals/utils/src/casing.ts
5
+ /**
6
+ * Shared implementation for camelCase and PascalCase conversion.
7
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
8
+ * and capitalizes each word according to `pascal`.
9
+ *
10
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
11
+ */
12
+ function convert(text, pascal) {
13
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
14
+ if (word.length > 1 && word === word.toUpperCase()) return word;
15
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
16
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
17
+ }
18
+ const cache = /* @__PURE__ */ new Map();
19
+ const cacheLimit = 1e5;
20
+ function toCamelOrPascal(text, pascal) {
21
+ const key = `${pascal ? "P" : "c"}${text}`;
22
+ const cached = cache.get(key);
23
+ if (cached !== void 0) return cached;
24
+ const result = convert(text, pascal);
25
+ if (cache.size >= cacheLimit) cache.clear();
26
+ cache.set(key, result);
27
+ return result;
28
+ }
29
+ /**
30
+ * Converts `text` to PascalCase.
31
+ *
32
+ * @example Word boundaries
33
+ * `pascalCase('hello-world') // 'HelloWorld'`
34
+ *
35
+ * @example With a suffix
36
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
37
+ */
38
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
39
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
40
+ }
41
+ // ../../internals/utils/src/fs.ts
6
42
  /**
7
43
  * Converts a filesystem path to use POSIX (`/`) separators.
8
44
  *
@@ -41,17 +77,14 @@ function isPathInside(path, parent) {
41
77
  const rel = relative(resolvedParent, resolvedPath);
42
78
  return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
43
79
  }
44
- //#endregion
45
- //#region kubb-extension.json
80
+ // kubb-extension.json
46
81
  var compatibility = {
47
82
  "kubb": ">=5.8.3",
48
83
  "node": ">=22.22"
49
84
  };
50
- //#endregion
51
- //#region package.json
52
- var version = "5.14.6";
53
- //#endregion
54
- //#region src/utils.ts
85
+ // package.json
86
+ var version = "5.15.1";
87
+ // src/utils.ts
55
88
  const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
56
89
  ".ts",
57
90
  ".tsx",
@@ -66,14 +99,30 @@ function sortedChildren(node) {
66
99
  function toRelativeModulePath(fromDir, filePath) {
67
100
  return `./${posix.relative(fromDir, filePath)}`;
68
101
  }
102
+ function getExportNames({ item, dirPath, ctx }) {
103
+ if (item.name != null) return [item.name].flat();
104
+ const itemPath = posix.join(dirPath, item.path);
105
+ const barrel = ctx.barrelFiles.get(itemPath);
106
+ if (barrel) return barrel.exports.flatMap((item) => getExportNames({
107
+ item,
108
+ dirPath: posix.dirname(barrel.path),
109
+ ctx
110
+ }));
111
+ return ctx.sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? [];
112
+ }
69
113
  /**
70
114
  * Builds the `index.ts` for `dirPath`. Keeps the first export of each name and reports later duplicates once per build.
71
115
  */
72
- function makeBarrel(dirPath, exports, { sourceFiles, reportedCollisions }) {
116
+ function makeBarrel(dirPath, exports, ctx) {
117
+ const { reportedCollisions } = ctx;
73
118
  const owners = /* @__PURE__ */ new Map();
74
119
  const uniqueExports = exports.flatMap((item) => {
75
120
  const itemPath = posix.join(dirPath, item.path);
76
- const uniqueNames = (item.name == null ? sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? [] : [item.name].flat()).filter((name) => {
121
+ const uniqueNames = getExportNames({
122
+ item,
123
+ dirPath,
124
+ ctx
125
+ }).filter((name) => {
77
126
  const first = owners.get(name);
78
127
  if (!first) {
79
128
  owners.set(name, item.path);
@@ -101,11 +150,13 @@ function makeBarrel(dirPath, exports, { sourceFiles, reportedCollisions }) {
101
150
  name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0]
102
151
  })] : [];
103
152
  });
104
- return ast.factory.createFile({
153
+ const file = ast.factory.createFile({
105
154
  baseName: "index.ts",
106
155
  path: `${dirPath}${BARREL_SUFFIX}`,
107
156
  exports: uniqueExports
108
157
  });
158
+ ctx.barrelFiles.set(file.path, file);
159
+ return file;
109
160
  }
110
161
  function indexableNames(sources, isTypeOnly) {
111
162
  const names = sources.flatMap((source) => source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []);
@@ -146,6 +197,7 @@ function walkFlat(node, ctx, isRoot = true) {
146
197
  const barrels = results.flatMap((result) => result.barrels);
147
198
  const leaves = results.flatMap((result) => result.leaves);
148
199
  const exports = isRoot || ctx.recursive ? leaves.flatMap((leaf) => ctx.leafExports(node.path, leaf)) : [];
200
+ if (isRoot) exports.push(...ctx.additionalExports);
149
201
  return {
150
202
  barrels: exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels,
151
203
  leaves
@@ -164,10 +216,25 @@ function walkNested(node, ctx) {
164
216
  barrels: [],
165
217
  exports: ctx.leafExports(node.path, child.path)
166
218
  };
167
- const barrels = walkNested(child, ctx);
219
+ const barrels = walkNested(child, {
220
+ ...ctx,
221
+ namespace: false
222
+ });
223
+ if (barrels.length === 0) return {
224
+ barrels,
225
+ exports: []
226
+ };
227
+ const modulePath = toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`);
228
+ const exports = [ast.factory.createExport({ path: modulePath })];
229
+ const name = pascalCase(posix.basename(child.path));
230
+ if (ctx.namespace) exports.push(ast.factory.createExport({
231
+ path: modulePath,
232
+ name: /^[A-Za-z]/.test(name) ? name : `_${name}`,
233
+ asAlias: true
234
+ }));
168
235
  return {
169
236
  barrels,
170
- exports: barrels.length > 0 ? [ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) })] : []
237
+ exports
171
238
  };
172
239
  });
173
240
  const barrels = results.flatMap((result) => result.barrels);
@@ -226,20 +293,22 @@ function findDirectory(tree, targetPath) {
226
293
  * getBarrelFiles({ index, targetPath, barrelType }).forEach((file) => upsertFile(file))
227
294
  * ```
228
295
  */
229
- function getBarrelFiles({ index, targetPath, barrelType, nested = false, recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
296
+ function getBarrelFiles({ index, targetPath, barrelType, nested = false, namespace = false, additionalExports = [], recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
230
297
  const node = targetPath ? findDirectory(index.tree, toPosixPath(targetPath)) : index.tree;
231
298
  if (!node) return [];
232
299
  const strategy = barrelType === "named" ? namedStrategy : allStrategy;
233
300
  const ctx = {
234
301
  sourceFiles: index.sourceFiles,
302
+ barrelFiles: /* @__PURE__ */ new Map(),
235
303
  reportedCollisions,
236
304
  recursive,
305
+ namespace,
306
+ additionalExports,
237
307
  leafExports: (dirPath, leafPath) => strategy(toRelativeModulePath(dirPath, leafPath), index.sourceFiles.get(leafPath))
238
308
  };
239
- return nested ? walkNested(node, ctx) : walkFlat(node, ctx).barrels;
309
+ return nested || namespace ? walkNested(node, ctx) : walkFlat(node, ctx).barrels;
240
310
  }
241
- //#endregion
242
- //#region src/plugin.ts
311
+ // src/plugin.ts
243
312
  /**
244
313
  * Applies a plugin's configured `output.banner`/`footer` to a barrel file, flagged as `isBarrel`.
245
314
  *
@@ -337,7 +406,15 @@ const pluginBarrel = defineExtension({
337
406
  if (target !== base && isPathInside(target, base)) excludedTargets.push(target);
338
407
  return;
339
408
  }
340
- if (target === base) return;
409
+ if (target === base) {
410
+ if (barrelConfig.namespace) Diagnostics.report({
411
+ code: Diagnostics.code.pluginWarning,
412
+ severity: "warning",
413
+ message: `Plugin "${plugin.name}" sets \`output.barrel.namespace\` but writes to the output root, so no namespaces are generated.`,
414
+ help: "Give the plugin its own `output.path`, such as `client`."
415
+ });
416
+ return;
417
+ }
341
418
  if (plugin.options.output.mode === "file") return;
342
419
  if (!isPathInside(target, base)) throw new Diagnostics.Error({
343
420
  code: Diagnostics.code.pathTraversal,
@@ -350,7 +427,8 @@ const pluginBarrel = defineExtension({
350
427
  plugin,
351
428
  target,
352
429
  barrelType: barrelConfig.type,
353
- nested: barrelConfig.nested ?? false
430
+ nested: barrelConfig.nested ?? false,
431
+ namespace: barrelConfig.namespace ?? false
354
432
  });
355
433
  },
356
434
  "kubb:plugins:end"({ files, config, upsertFile }) {
@@ -366,12 +444,14 @@ const pluginBarrel = defineExtension({
366
444
  excludedTargets.length = 0;
367
445
  const index = buildBarrelIndex(outputPath, relevantFiles);
368
446
  const reportedCollisions = /* @__PURE__ */ new Set();
369
- pendingBarrels.forEach(({ plugin, target, barrelType, nested }) => {
447
+ const namespaceExports = /* @__PURE__ */ new Map();
448
+ pendingBarrels.forEach(({ plugin, target, barrelType, nested, namespace }) => {
370
449
  getBarrelFiles({
371
450
  index,
372
451
  targetPath: target,
373
452
  barrelType,
374
453
  nested,
454
+ namespace,
375
455
  recursive: true,
376
456
  reportedCollisions
377
457
  }).forEach((file) => {
@@ -380,6 +460,14 @@ const pluginBarrel = defineExtension({
380
460
  plugin,
381
461
  config
382
462
  }));
463
+ if (file.path !== `${toPosixPath(target)}/index.ts`) return;
464
+ file.exports.filter((item) => item.asAlias).forEach((item) => {
465
+ const itemPath = `./${toPosixPath(path.relative(outputPath, path.resolve(target, item.path)))}`;
466
+ namespaceExports.set(`${itemPath}\0${item.name}`, ast.factory.createExport({
467
+ ...item,
468
+ path: itemPath
469
+ }));
470
+ });
383
471
  });
384
472
  });
385
473
  pendingBarrels.length = 0;
@@ -387,6 +475,7 @@ const pluginBarrel = defineExtension({
387
475
  getBarrelFiles({
388
476
  index,
389
477
  barrelType: rootBarrelConfig.type,
478
+ additionalExports: [...namespaceExports.values()],
390
479
  reportedCollisions
391
480
  }).forEach((file) => {
392
481
  upsertFile(file);
@@ -394,7 +483,6 @@ const pluginBarrel = defineExtension({
394
483
  }
395
484
  }
396
485
  });
397
- //#endregion
398
486
  export { pluginBarrel, pluginBarrelName };
399
487
 
400
488
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/fs.ts","../kubb-extension.json","../package.json","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\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\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","","","import { extname, posix } from 'node:path'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport { 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\n/**\n * A file (no `children`) or directory in the tree barrels are computed from. `path` is absolute and\n * POSIX-normalized, and a directory's `children` are keyed by entry name.\n */\ntype TreeNode = { path: string; children?: undefined } | DirectoryNode\ntype DirectoryNode = { path: string; children: Map<string, TreeNode> }\n\n/** A directory's children in name order, the way a directory listing sorts them. */\nfunction sortedChildren(node: DirectoryNode): Array<TreeNode> {\n return [...node.children.entries()].sort(([a], [b]) => (a < b ? -1 : 1)).map(([, child]) => child)\n}\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\ntype WalkContext = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n reportedCollisions: Set<string>\n recursive: boolean\n leafExports: (dirPath: string, leafPath: string) => Array<ast.ExportNode>\n}\n\n/**\n * Builds the `index.ts` for `dirPath`. Keeps the first export of each name and reports later duplicates once per build.\n */\nfunction makeBarrel(dirPath: string, exports: Array<ast.ExportNode>, { sourceFiles, reportedCollisions }: WalkContext): ast.FileNode {\n const owners = new Map<string, string>()\n const uniqueExports = exports.flatMap((item) => {\n const itemPath = posix.join(dirPath, item.path)\n // A wildcard export carries no names, so collisions are checked against its source file.\n const names = item.name == null ? (sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? []) : [item.name].flat()\n const uniqueNames = names.filter((name) => {\n const first = owners.get(name)\n if (!first) {\n owners.set(name, item.path)\n return true\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (!reportedCollisions.has(key)) {\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n return false\n })\n\n if (item.name == null) return [item]\n return uniqueNames.length > 0 ? [ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] })] : []\n })\n\n return ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n}\n\ntype LeafStrategy = (path: string, sourceFile: ast.FileNode | undefined) => Array<ast.ExportNode>\n\nfunction indexableNames(sources: ReadonlyArray<ast.SourceNode>, isTypeOnly: boolean): Array<string> {\n const names = sources.flatMap((source) => (source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []))\n return [...new Set(names)].sort()\n}\n\n/**\n * Re-exports the whole module, unless it has sources and none of them is indexable.\n */\nconst allStrategy: LeafStrategy = (path, sourceFile) => {\n const sources = sourceFile?.sources ?? []\n return sources.length > 0 && !sources.some((source) => source.isIndexable) ? [] : [ast.factory.createExport({ path })]\n}\n\n/**\n * Re-exports the indexable names, values and types separately, or the whole module when it has no sources.\n */\nconst namedStrategy: LeafStrategy = (path, sourceFile) => {\n if (!sourceFile?.sources.length) return [ast.factory.createExport({ path })]\n\n const valueNames = indexableNames(sourceFile.sources, false)\n const typeNames = indexableNames(sourceFile.sources, true)\n return [\n ...(valueNames.length > 0 ? [ast.factory.createExport({ name: valueNames, path })] : []),\n ...(typeNames.length > 0 ? [ast.factory.createExport({ name: typeNames, path, isTypeOnly: true })] : []),\n ]\n}\n\ntype FlatWalk = { barrels: Array<ast.FileNode>; leaves: Array<string> }\n\n/**\n * Post-order walk that builds a barrel re-exporting every leaf below the visited directory, for\n * the start node and, with `recursive`, every sub-directory. Also returns the subtree's leaf paths.\n */\nfunction walkFlat(node: DirectoryNode, ctx: WalkContext, isRoot = true): FlatWalk {\n const results = sortedChildren(node).map((child): FlatWalk => (child.children ? walkFlat(child, ctx, false) : { barrels: [], leaves: [child.path] }))\n const barrels = results.flatMap((result) => result.barrels)\n const leaves = results.flatMap((result) => result.leaves)\n const exports = isRoot || ctx.recursive ? leaves.flatMap((leaf) => ctx.leafExports(node.path, leaf)) : []\n\n return { barrels: exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels, leaves }\n}\n\n/**\n * Recursive walk that builds one barrel per directory, re-exporting files and sub-barrels.\n * Used when nested: true. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. A sub-directory that\n * produced no barrel is never re-exported.\n */\nfunction walkNested(node: DirectoryNode, ctx: WalkContext): Array<ast.FileNode> {\n const results = sortedChildren(node).map((child) => {\n if (!child.children) return { barrels: [], exports: ctx.leafExports(node.path, child.path) }\n\n // A sub-directory has barrels only when it has its own `index.ts`.\n const barrels = walkNested(child, ctx)\n const exports = barrels.length > 0 ? [ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) })] : []\n return { barrels, exports }\n })\n const barrels = results.flatMap((result) => result.barrels)\n const exports = results.flatMap((result) => result.exports)\n\n return exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels\n}\n\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: DirectoryNode\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<ast.FileNode>): BarrelIndex {\n const tree: DirectoryNode = { path: toPosixPath(outputPath), children: new Map() }\n const sourceFiles = new Map(\n files.flatMap((file) => {\n const filePath = toPosixPath(file.path)\n const isSource = filePath.startsWith(`${tree.path}/`) && !filePath.endsWith(BARREL_SUFFIX) && SOURCE_EXTENSIONS.has(extname(filePath))\n return isSource ? [[filePath, file] as const] : []\n }),\n )\n\n for (const filePath of sourceFiles.keys()) {\n const parts = filePath\n .slice(tree.path.length + 1)\n .split('/')\n .filter(Boolean)\n parts.reduce<DirectoryNode>((dir, part, i) => {\n const path = `${dir.path}/${part}`\n const child: TreeNode = dir.children.get(part) ?? (i === parts.length - 1 ? { path } : { path, children: new Map() })\n dir.children.set(part, child)\n return child.children ? child : dir\n }, tree)\n }\n\n return { tree, sourceFiles }\n}\n\n/**\n * Locates the directory node for `targetPath` within an index tree. Returns `undefined` when no\n * file exists under `targetPath` (nothing to barrel).\n */\nfunction findDirectory(tree: DirectoryNode, targetPath: string): DirectoryNode | undefined {\n if (targetPath === tree.path) return tree\n if (!targetPath.startsWith(`${tree.path}/`)) return undefined\n\n const node = targetPath\n .slice(tree.path.length + 1)\n .split('/')\n .reduce<TreeNode | undefined>((current, part) => current?.children?.get(part), tree)\n return node?.children ? node : undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /** Absolute directory to root the barrel(s) at, a subtree of the index root. Defaults to the index root. */\n targetPath?: string\n /** `'all'` re-exports whole modules (`export * from './x'`), `'named'` only the indexable named symbols. */\n barrelType: BarrelType\n /** One `index.ts` per sub-directory, each re-exporting only what is directly inside it. */\n nested?: boolean\n /** Also generate a barrel for each sub-directory when `nested` is false. */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Returns the barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * getBarrelFiles({ index, targetPath, barrelType }).forEach((file) => upsertFile(file))\n * ```\n */\nexport function getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Array<ast.FileNode> {\n const node = targetPath ? findDirectory(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return []\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n const ctx: WalkContext = {\n sourceFiles: index.sourceFiles,\n reportedCollisions,\n recursive,\n leafExports: (dirPath, leafPath) => strategy(toRelativeModulePath(dirPath, leafPath), index.sourceFiles.get(leafPath)),\n }\n\n return nested ? walkNested(node, ctx) : walkFlat(node, ctx).barrels\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport { defineExtension } from '@kubb/core'\nimport { Diagnostics } from '@kubb/kit'\nimport type { ast, Config, NormalizedPlugin, Plugin } from '@kubb/kit'\nimport { compatibility } from '../kubb-extension.json'\nimport { version } from '../package.json'\nimport type { BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles } from './utils.ts'\n\n/**\n * Applies a plugin's configured `output.banner`/`footer` to a barrel file, flagged as `isBarrel`.\n *\n * Resolves through the plugin's own resolver, and only when the plugin explicitly sets a\n * banner/footer, so barrels stay banner-free by default and never inherit the implicit\n * \"Generated by Kubb\" notice.\n */\nfunction withBarrelBannerFooter({ file, plugin, config }: { file: ast.FileNode; plugin: NormalizedPlugin; config: Config }): ast.FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!resolver || (!hasBanner && !hasFooter)) return file\n\n const context = { output, config, file: { path: file.path, baseName: file.baseName, isBarrel: true } }\n return {\n ...file,\n banner: hasBanner ? resolver.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(undefined, context) : file.footer,\n }\n}\n\n/**\n * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups\n * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n}\n\ntype BarrelState = {\n excludedPlugins: Set<string>\n // Subdirectories of `barrel: false` plugins, for files without a `meta.pluginName` owner.\n excludedTargets: Array<string>\n pendingBarrels: Array<PendingBarrel>\n}\n\n// One state per build, keyed by the build's `config`. The object form has no factory closure to hold it.\nconst states = new WeakMap<Config, BarrelState>()\n\nfunction getState(config: Config): BarrelState {\n const state = states.get(config) ?? { excludedPlugins: new Set(), excludedTargets: [], pendingBarrels: [] }\n states.set(config, state)\n return state\n}\n\n/**\n * Generates an `index.ts` for every plugin output directory and one root\n * barrel at `config.output.path/index.ts` after the build completes. Ships\n * with Kubb and is registered by default in `defineConfig`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * from the root barrel.\n *\n * A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output\n * is a single file. The root barrel re-exports that file directly.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@kubb/core'\n * import { pluginBarrel } from '@kubb/plugin-barrel'\n * import { pluginTs } from '@kubb/plugin-ts'\n * import { pluginZod } from '@kubb/plugin-zod'\n *\n * export default defineConfig({\n * input: './petStore.yaml',\n * output: { path: 'src/gen', barrel: { type: 'named' } },\n * plugins: [\n * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),\n * pluginZod({ output: { path: 'schemas' } }),\n * pluginBarrel(),\n * ],\n * })\n * ```\n */\nexport const pluginBarrel = defineExtension({\n kind: 'plugin',\n meta: { name: pluginBarrelName, version, compatibility, issues: 'https://github.com/kubb-labs/kubb/issues' },\n enforce: 'post',\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n\n // A plugin-level `false` wins over an enabled root barrel.\n const barrelConfig: PluginBarrelConfig | false = plugin.options.output?.barrel ?? config.output.barrel ?? false\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path ?? '.')\n\n if (barrelConfig === false) {\n excludedPlugins.add(plugin.name)\n // The output root holds every plugin's files, so there only the owner match applies.\n if (target !== base && isPathInside(target, base)) {\n excludedTargets.push(target)\n }\n return\n }\n\n // A plugin at the output root (no own `output.path`) gets no per-plugin barrel, which would\n // claim every other plugin's directory. The root barrel picks up its files.\n if (target === base) {\n return\n }\n\n // `mode: 'file'` writes a single file, so there is no directory to barrel. The root barrel\n // re-exports that file as a direct leaf of `config.output.path`.\n if (plugin.options.output.mode === 'file') {\n return\n }\n\n if (!isPathInside(target, base)) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.pathTraversal,\n severity: 'error',\n message: `Output path \"${plugin.options.output.path}\" of plugin \"${plugin.name}\" resolves outside the output directory \"${base}\".`,\n help: 'Keep the plugin `output.path` within `config.output.path`.',\n location: { kind: 'config' },\n })\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = files.filter((file) => {\n const owner = (file.meta as { pluginName?: string } | undefined)?.pluginName\n if (owner) return !excludedPlugins.has(owner)\n\n return !excludedTargets.some((target) => isPathInside(file.path, target))\n })\n excludedPlugins.clear()\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n pendingBarrels.forEach(({ plugin, target, barrelType, nested }) => {\n getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions }).forEach((file) => {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n })\n })\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions }).forEach((file) => {\n upsertFile(file)\n })\n },\n },\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/casing.ts","../../../internals/utils/src/fs.ts","../kubb-extension.json","../package.json","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["type Options = {\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction convert(text: string, pascal: boolean): string {\n return text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n .split(/[\\s\\-_./\\\\:]+/)\n .filter(Boolean)\n .map((word, i) => {\n if (word.length > 1 && word === word.toUpperCase()) return word\n const head = i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()\n return head + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n// Plugins case the same names on every visit. Clearing at the limit bounds a long-running host.\nconst cache = new Map<string, string>()\nconst cacheLimit = 100_000\n\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const key = `${pascal ? 'P' : 'c'}${text}`\n const cached = cache.get(key)\n if (cached !== undefined) return cached\n\n const result = convert(text, pascal)\n if (cache.size >= cacheLimit) cache.clear()\n cache.set(key, result)\n return result\n}\n\n/**\n * Converts `text` to camelCase.\n *\n * @example Word boundaries\n * `camelCase('hello-world') // 'helloWorld'`\n *\n * @example With a prefix\n * `camelCase('tag', { prefix: 'create' }) // 'createTag'`\n */\nexport function camelCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n *\n * @example Word boundaries\n * `pascalCase('hello-world') // 'HelloWorld'`\n *\n * @example With a suffix\n * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`\n */\nexport function pascalCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\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\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","","","import { extname, posix } from 'node:path'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport { pascalCase, 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\n/**\n * A file (no `children`) or directory in the tree barrels are computed from. `path` is absolute and\n * POSIX-normalized, and a directory's `children` are keyed by entry name.\n */\ntype TreeNode = { path: string; children?: undefined } | DirectoryNode\ntype DirectoryNode = { path: string; children: Map<string, TreeNode> }\n\n/** A directory's children in name order, the way a directory listing sorts them. */\nfunction sortedChildren(node: DirectoryNode): Array<TreeNode> {\n return [...node.children.entries()].sort(([a], [b]) => (a < b ? -1 : 1)).map(([, child]) => child)\n}\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\ntype WalkContext = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n barrelFiles: Map<string, ast.FileNode>\n reportedCollisions: Set<string>\n recursive: boolean\n namespace: boolean\n additionalExports: ReadonlyArray<ast.ExportNode>\n leafExports: (dirPath: string, leafPath: string) => Array<ast.ExportNode>\n}\n\nfunction getExportNames({ item, dirPath, ctx }: { item: ast.ExportNode; dirPath: string; ctx: WalkContext }): Array<string> {\n if (item.name != null) return [item.name].flat()\n\n const itemPath = posix.join(dirPath, item.path)\n const barrel = ctx.barrelFiles.get(itemPath)\n if (barrel) return barrel.exports.flatMap((item) => getExportNames({ item, dirPath: posix.dirname(barrel.path), ctx }))\n\n return ctx.sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name || []) ?? []\n}\n\n/**\n * Builds the `index.ts` for `dirPath`. Keeps the first export of each name and reports later duplicates once per build.\n */\nfunction makeBarrel(dirPath: string, exports: Array<ast.ExportNode>, ctx: WalkContext): ast.FileNode {\n const { reportedCollisions } = ctx\n const owners = new Map<string, string>()\n const uniqueExports = exports.flatMap((item) => {\n const itemPath = posix.join(dirPath, item.path)\n // A wildcard export carries no names, so collisions are checked against its source file.\n const names = getExportNames({ item, dirPath, ctx })\n const uniqueNames = names.filter((name) => {\n const first = owners.get(name)\n if (!first) {\n owners.set(name, item.path)\n return true\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (!reportedCollisions.has(key)) {\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n return false\n })\n\n if (item.name == null) return [item]\n return uniqueNames.length > 0 ? [ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] })] : []\n })\n\n const file = ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n ctx.barrelFiles.set(file.path, file)\n return file\n}\n\ntype LeafStrategy = (path: string, sourceFile: ast.FileNode | undefined) => Array<ast.ExportNode>\n\nfunction indexableNames(sources: ReadonlyArray<ast.SourceNode>, isTypeOnly: boolean): Array<string> {\n const names = sources.flatMap((source) => (source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []))\n return [...new Set(names)].sort()\n}\n\n/**\n * Re-exports the whole module, unless it has sources and none of them is indexable.\n */\nconst allStrategy: LeafStrategy = (path, sourceFile) => {\n const sources = sourceFile?.sources ?? []\n return sources.length > 0 && !sources.some((source) => source.isIndexable) ? [] : [ast.factory.createExport({ path })]\n}\n\n/**\n * Re-exports the indexable names, values and types separately, or the whole module when it has no sources.\n */\nconst namedStrategy: LeafStrategy = (path, sourceFile) => {\n if (!sourceFile?.sources.length) return [ast.factory.createExport({ path })]\n\n const valueNames = indexableNames(sourceFile.sources, false)\n const typeNames = indexableNames(sourceFile.sources, true)\n return [\n ...(valueNames.length > 0 ? [ast.factory.createExport({ name: valueNames, path })] : []),\n ...(typeNames.length > 0 ? [ast.factory.createExport({ name: typeNames, path, isTypeOnly: true })] : []),\n ]\n}\n\ntype FlatWalk = { barrels: Array<ast.FileNode>; leaves: Array<string> }\n\n/**\n * Post-order walk that builds a barrel re-exporting every leaf below the visited directory, for\n * the start node and, with `recursive`, every sub-directory. Also returns the subtree's leaf paths.\n */\nfunction walkFlat(node: DirectoryNode, ctx: WalkContext, isRoot = true): FlatWalk {\n const results = sortedChildren(node).map((child): FlatWalk => (child.children ? walkFlat(child, ctx, false) : { barrels: [], leaves: [child.path] }))\n const barrels = results.flatMap((result) => result.barrels)\n const leaves = results.flatMap((result) => result.leaves)\n const exports = isRoot || ctx.recursive ? leaves.flatMap((leaf) => ctx.leafExports(node.path, leaf)) : []\n if (isRoot) exports.push(...ctx.additionalExports)\n\n return { barrels: exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels, leaves }\n}\n\n/**\n * Recursive walk that builds one barrel per directory, re-exporting files and sub-barrels.\n * Used when nested: true. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. A sub-directory that\n * produced no barrel is never re-exported.\n */\nfunction walkNested(node: DirectoryNode, ctx: WalkContext): Array<ast.FileNode> {\n const results = sortedChildren(node).map((child) => {\n if (!child.children) return { barrels: [], exports: ctx.leafExports(node.path, child.path) }\n\n // A sub-directory has barrels only when it has its own `index.ts`.\n const barrels = walkNested(child, { ...ctx, namespace: false })\n if (barrels.length === 0) return { barrels, exports: [] }\n\n const modulePath = toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`)\n const exports = [ast.factory.createExport({ path: modulePath })]\n const name = pascalCase(posix.basename(child.path))\n if (ctx.namespace) exports.push(ast.factory.createExport({ path: modulePath, name: /^[A-Za-z]/.test(name) ? name : `_${name}`, asAlias: true }))\n return { barrels, exports }\n })\n const barrels = results.flatMap((result) => result.barrels)\n const exports = results.flatMap((result) => result.exports)\n\n return exports.length > 0 ? [...barrels, makeBarrel(node.path, exports, ctx)] : barrels\n}\n\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: DirectoryNode\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<ast.FileNode>): BarrelIndex {\n const tree: DirectoryNode = { path: toPosixPath(outputPath), children: new Map() }\n const sourceFiles = new Map(\n files.flatMap((file) => {\n const filePath = toPosixPath(file.path)\n const isSource = filePath.startsWith(`${tree.path}/`) && !filePath.endsWith(BARREL_SUFFIX) && SOURCE_EXTENSIONS.has(extname(filePath))\n return isSource ? [[filePath, file] as const] : []\n }),\n )\n\n for (const filePath of sourceFiles.keys()) {\n const parts = filePath\n .slice(tree.path.length + 1)\n .split('/')\n .filter(Boolean)\n parts.reduce<DirectoryNode>((dir, part, i) => {\n const path = `${dir.path}/${part}`\n const child: TreeNode = dir.children.get(part) ?? (i === parts.length - 1 ? { path } : { path, children: new Map() })\n dir.children.set(part, child)\n return child.children ? child : dir\n }, tree)\n }\n\n return { tree, sourceFiles }\n}\n\n/**\n * Locates the directory node for `targetPath` within an index tree. Returns `undefined` when no\n * file exists under `targetPath` (nothing to barrel).\n */\nfunction findDirectory(tree: DirectoryNode, targetPath: string): DirectoryNode | undefined {\n if (targetPath === tree.path) return tree\n if (!targetPath.startsWith(`${tree.path}/`)) return undefined\n\n const node = targetPath\n .slice(tree.path.length + 1)\n .split('/')\n .reduce<TreeNode | undefined>((current, part) => current?.children?.get(part), tree)\n return node?.children ? node : undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /** Absolute directory to root the barrel(s) at, a subtree of the index root. Defaults to the index root. */\n targetPath?: string\n /** `'all'` re-exports whole modules (`export * from './x'`), `'named'` only the indexable named symbols. */\n barrelType: BarrelType\n /** One `index.ts` per sub-directory, each re-exporting only what is directly inside it. */\n nested?: boolean\n /** Add PascalCase namespace exports for immediate subdirectories and enable nested barrels. */\n namespace?: boolean\n /** Extra exports to forward from plugin barrels into the root barrel. */\n additionalExports?: ReadonlyArray<ast.ExportNode>\n /** Also generate a barrel for each sub-directory when `nested` is false. */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Returns the barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * getBarrelFiles({ index, targetPath, barrelType }).forEach((file) => upsertFile(file))\n * ```\n */\nexport function getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n namespace = false,\n additionalExports = [],\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Array<ast.FileNode> {\n const node = targetPath ? findDirectory(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return []\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n const ctx: WalkContext = {\n sourceFiles: index.sourceFiles,\n barrelFiles: new Map(),\n reportedCollisions,\n recursive,\n namespace,\n additionalExports,\n leafExports: (dirPath, leafPath) => strategy(toRelativeModulePath(dirPath, leafPath), index.sourceFiles.get(leafPath)),\n }\n\n return nested || namespace ? walkNested(node, ctx) : walkFlat(node, ctx).barrels\n}\n","import path from 'node:path'\nimport { isPathInside, toPosixPath } from '@internals/utils'\nimport { defineExtension } from '@kubb/core'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/kit'\nimport { compatibility } from '../kubb-extension.json'\nimport { version } from '../package.json'\nimport type { BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles } from './utils.ts'\n\n/**\n * Applies a plugin's configured `output.banner`/`footer` to a barrel file, flagged as `isBarrel`.\n *\n * Resolves through the plugin's own resolver, and only when the plugin explicitly sets a\n * banner/footer, so barrels stay banner-free by default and never inherit the implicit\n * \"Generated by Kubb\" notice.\n */\nfunction withBarrelBannerFooter({ file, plugin, config }: { file: ast.FileNode; plugin: NormalizedPlugin; config: Config }): ast.FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!resolver || (!hasBanner && !hasFooter)) return file\n\n const context = { output, config, file: { path: file.path, baseName: file.baseName, isBarrel: true } }\n return {\n ...file,\n banner: hasBanner ? resolver.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(undefined, context) : file.footer,\n }\n}\n\n/**\n * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups\n * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n namespace: boolean\n}\n\ntype BarrelState = {\n excludedPlugins: Set<string>\n // Subdirectories of `barrel: false` plugins, for files without a `meta.pluginName` owner.\n excludedTargets: Array<string>\n pendingBarrels: Array<PendingBarrel>\n}\n\n// One state per build, keyed by the build's `config`. The object form has no factory closure to hold it.\nconst states = new WeakMap<Config, BarrelState>()\n\nfunction getState(config: Config): BarrelState {\n const state = states.get(config) ?? { excludedPlugins: new Set(), excludedTargets: [], pendingBarrels: [] }\n states.set(config, state)\n return state\n}\n\n/**\n * Generates an `index.ts` for every plugin output directory and one root\n * barrel at `config.output.path/index.ts` after the build completes. Ships\n * with Kubb and is registered by default in `defineConfig`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * from the root barrel.\n *\n * A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output\n * is a single file. The root barrel re-exports that file directly.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@kubb/core'\n * import { pluginBarrel } from '@kubb/plugin-barrel'\n * import { pluginTs } from '@kubb/plugin-ts'\n * import { pluginZod } from '@kubb/plugin-zod'\n *\n * export default defineConfig({\n * input: './petStore.yaml',\n * output: { path: 'src/gen', barrel: { type: 'named' } },\n * plugins: [\n * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),\n * pluginZod({ output: { path: 'schemas' } }),\n * pluginBarrel(),\n * ],\n * })\n * ```\n */\nexport const pluginBarrel = defineExtension({\n kind: 'plugin',\n meta: { name: pluginBarrelName, version, compatibility, issues: 'https://github.com/kubb-labs/kubb/issues' },\n enforce: 'post',\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n\n // A plugin-level `false` wins over an enabled root barrel.\n const barrelConfig: PluginBarrelConfig | false = plugin.options.output?.barrel ?? config.output.barrel ?? false\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path ?? '.')\n\n if (barrelConfig === false) {\n excludedPlugins.add(plugin.name)\n // The output root holds every plugin's files, so there only the owner match applies.\n if (target !== base && isPathInside(target, base)) {\n excludedTargets.push(target)\n }\n return\n }\n\n // A plugin at the output root (no own `output.path`) gets no per-plugin barrel, which would\n // claim every other plugin's directory. The root barrel picks up its files.\n if (target === base) {\n if (barrelConfig.namespace) {\n Diagnostics.report({\n code: Diagnostics.code.pluginWarning,\n severity: 'warning',\n message: `Plugin \"${plugin.name}\" sets \\`output.barrel.namespace\\` but writes to the output root, so no namespaces are generated.`,\n help: 'Give the plugin its own `output.path`, such as `client`.',\n })\n }\n return\n }\n\n // `mode: 'file'` writes a single file, so there is no directory to barrel. The root barrel\n // re-exports that file as a direct leaf of `config.output.path`.\n if (plugin.options.output.mode === 'file') {\n return\n }\n\n if (!isPathInside(target, base)) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.pathTraversal,\n severity: 'error',\n message: `Output path \"${plugin.options.output.path}\" of plugin \"${plugin.name}\" resolves outside the output directory \"${base}\".`,\n help: 'Keep the plugin `output.path` within `config.output.path`.',\n location: { kind: 'config' },\n })\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false, namespace: barrelConfig.namespace ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const { excludedPlugins, excludedTargets, pendingBarrels } = getState(config)\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = files.filter((file) => {\n const owner = (file.meta as { pluginName?: string } | undefined)?.pluginName\n if (owner) return !excludedPlugins.has(owner)\n\n return !excludedTargets.some((target) => isPathInside(file.path, target))\n })\n excludedPlugins.clear()\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n // Keyed by path and name: plugins sharing an `output.path` produce the same aliases.\n const namespaceExports = new Map<string, ast.ExportNode>()\n\n pendingBarrels.forEach(({ plugin, target, barrelType, nested, namespace }) => {\n getBarrelFiles({ index, targetPath: target, barrelType, nested, namespace, recursive: true, reportedCollisions }).forEach((file) => {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n if (file.path !== `${toPosixPath(target)}/index.ts`) return\n file.exports\n .filter((item) => item.asAlias)\n .forEach((item) => {\n const itemPath = `./${toPosixPath(path.relative(outputPath, path.resolve(target, item.path)))}`\n namespaceExports.set(`${itemPath}\\0${item.name}`, ast.factory.createExport({ ...item, path: itemPath }))\n })\n })\n })\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n getBarrelFiles({ index, barrelType: rootBarrelConfig.type, additionalExports: [...namespaceExports.values()], reportedCollisions }).forEach((file) => {\n upsertFile(file)\n })\n },\n },\n})\n"],"mappings":"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"}
package/docs/options.md CHANGED
@@ -4,11 +4,12 @@
4
4
 
5
5
  ## Options overview
6
6
 
7
- | Option | Purpose | Default |
8
- | ----------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
9
- | [`output.barrel`](#output-barrel) | Re-export style for the barrel files. | `false` on `defineConfig`, `{ type: 'named' }` in every generator plugin's default `output` |
10
- | ↳ [`output.barrel.type`](#type) | Named exports or a wildcard export. | Required when `output.barrel` is an object |
11
- | ↳ [`output.barrel.nested`](#nested) | Choose whether barrels reference subdirectory barrels. | `false` |
7
+ | Option | Purpose | Default |
8
+ | ----------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
9
+ | [`output.barrel`](#output-barrel) | Re-export style for the barrel files. | `false` on `defineConfig`, `{ type: 'named' }` in every generator plugin's default `output` |
10
+ | ↳ [`output.barrel.type`](#type) | Named exports or a wildcard export. | Required when `output.barrel` is an object |
11
+ | ↳ [`output.barrel.nested`](#nested) | Choose whether barrels reference subdirectory barrels. | `false` |
12
+ | ↳ [`output.barrel.namespace`](#namespace) | Expose each immediate group as a named namespace. | `false` |
12
13
 
13
14
  ## Option details
14
15
 
@@ -33,13 +34,13 @@ src/gen
33
34
  └── getInventory.ts
34
35
  ```
35
36
 
36
- | | |
37
- | -------- | ------------------------------------------------------- |
38
- | Type | `{ type: 'all' \| 'named', nested?: boolean } \| false` |
39
- | Required | `false` |
40
- | Default | `false` |
37
+ | | |
38
+ | -------- | ---------------------------------------------------------------------------- |
39
+ | Type | `{ type: 'all' \| 'named', nested?: boolean, namespace?: boolean } \| false` |
40
+ | Required | `false` |
41
+ | Default | `false` |
41
42
 
42
- The `type` field picks the export style. A plugin's `output.barrel` also accepts `nested`, so each barrel re-exports the barrels below it instead of the leaf files. The root `output.barrel` has no `nested` field and always stays flat.
43
+ The `type` field picks the export style. A plugin's `output.barrel` also accepts `nested`, so each barrel re-exports the barrels below it instead of the leaf files. Set `namespace: true` to add named group exports alongside the standalone exports. The root `output.barrel` accepts only `type` and always stays flat.
43
44
 
44
45
  A plugin called without `output` uses its own default, which sets `barrel: { type: 'named' }`. If you pass your own `output` object, it replaces that default. `pluginTs({ output: { path: 'types' } })` then has no `barrel` and uses `config.output.barrel`, which is `false` unless you set it.
45
46
 
@@ -117,3 +118,53 @@ export * from './User'
117
118
  ```
118
119
 
119
120
  ::
121
+
122
+ ### namespace
123
+
124
+ Expose each immediate subdirectory as a named namespace, alongside its standalone exports. Editors can offer auto-imports for these namespaces when the generated files belong to the TypeScript project. This field works on a plugin's `output.barrel` only and enables nested barrels, even when `nested` is unset or `false`. The plugin needs its own `output.path`. A plugin that writes to the output root gets a warning and no namespaces.
125
+
126
+ | | |
127
+ | -------- | --------- |
128
+ | Type | `boolean` |
129
+ | Required | `false` |
130
+ | Default | `false` |
131
+
132
+ For tag-grouped fetch clients, use `group.name` to name the service directories. Leave fetch `sdk` unset so React Query can import the standalone operation functions.
133
+
134
+ ```typescript [kubb.config.ts]
135
+ import { defineConfig } from 'kubb/config'
136
+ import { pluginFetch } from '@kubb/plugin-fetch'
137
+ import { pluginReactQuery } from '@kubb/plugin-react-query'
138
+ import { pluginTs } from '@kubb/plugin-ts'
139
+
140
+ export default defineConfig({
141
+ input: './petStore.yaml',
142
+ output: { path: './src/gen', barrel: { type: 'named' } },
143
+ plugins: [
144
+ pluginTs({ output: { path: 'types' } }),
145
+ pluginFetch({
146
+ output: { path: 'client', barrel: { type: 'named', namespace: true } },
147
+ group: { type: 'tag', name: ({ group }) => `${group}Service` },
148
+ }),
149
+ pluginReactQuery({ output: { path: 'hooks' } }),
150
+ ],
151
+ })
152
+ ```
153
+
154
+ For a `pet` tag, the operations land in `src/gen/client/petService/`. `src/gen/client/index.ts` exports both the operations and their PascalCase namespace:
155
+
156
+ ```typescript [src/gen/client/index.ts]
157
+ export * from './petService/index'
158
+ export * as PetService from './petService/index'
159
+ ```
160
+
161
+ Both calling styles remain available alongside the generated hooks and query options:
162
+
163
+ ```typescript [src/example.ts]
164
+ import { PetService, getPet } from './gen/client'
165
+
166
+ await PetService.getPet({ path: { petId: '123' } })
167
+ await getPet({ path: { petId: '123' } })
168
+ ```
169
+
170
+ An enabled root barrel also forwards `PetService`. Namespace names use the PascalCase directory name. Names starting with a digit get an `_` prefix, and names with no letters or digits become `_`. Conflicting exports produce a diagnostic. Change `group.name` to resolve collisions.
package/docs/overview.md CHANGED
@@ -12,6 +12,7 @@ Kubb adds the plugin to `plugins` for you, so no setup is needed. Generator plug
12
12
 
13
13
  - `type: 'named'` re-exports each symbol by name, which suits tree-shaking. `type: 'all'` writes `export *`.
14
14
  - `nested: true`, on a plugin only, makes each barrel re-export just its own folder and the barrels below it. The root barrel is always flat.
15
+ - `namespace: true`, on a plugin only, enables nested barrels and adds named namespaces for immediate subdirectories, alongside standalone exports.
15
16
  - `false` on a plugin turns its barrel off and leaves its files out of the root barrel.
16
17
 
17
18
  A plugin with `output.mode: 'file'` gets no barrel of its own, and the root barrel re-exports that file directly. Passing your own `output` object to a plugin replaces its default `barrel: { type: 'named' }`, so that plugin falls back to the root setting. When two files export the same name, Kubb reports an error, and a `named` barrel keeps only the first one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-barrel",
3
- "version": "5.14.6",
3
+ "version": "5.15.1",
4
4
  "description": "Barrel-file plugin for Kubb. Automatically generates index.ts re-export files per plugin output directory.",
5
5
  "keywords": [
6
6
  "barrel",
@@ -66,8 +66,8 @@
66
66
  "registry": "https://registry.npmjs.org/"
67
67
  },
68
68
  "dependencies": {
69
- "@kubb/core": "5.14.6",
70
- "@kubb/kit": "5.14.6"
69
+ "@kubb/core": "5.15.1",
70
+ "@kubb/kit": "5.15.1"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@internals/utils": "0.0.1"