@kubb/plugin-barrel 5.5.6 → 5.6.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/dist/index.cjs CHANGED
@@ -23,8 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let node_path = require("node:path");
25
25
  node_path = __toESM(node_path, 1);
26
- let _kubb_core = require("@kubb/core");
27
- let _kubb_ast = require("@kubb/ast");
26
+ let _kubb_kit = require("@kubb/kit");
28
27
  //#region ../../internals/utils/src/fs.ts
29
28
  /**
30
29
  * Converts a filesystem path to use POSIX (`/`) separators.
@@ -104,7 +103,6 @@ function buildTree(rootPath, filePaths) {
104
103
  const normalized = toPosixPath(filePath);
105
104
  if (!normalized.startsWith(rootPrefix)) continue;
106
105
  const parts = normalized.slice(rootPrefix.length).split("/");
107
- if (parts.length === 0) continue;
108
106
  let current = root;
109
107
  const lastIndex = parts.length - 1;
110
108
  for (const [i, part] of parts.entries()) {
@@ -136,17 +134,17 @@ function sortTree(node) {
136
134
  function compareByPath(a, b) {
137
135
  return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
138
136
  }
139
- function toRelativeModulePath(fromDir, filePath) {
140
- return `./${filePath.slice(fromDir.length + 1)}`;
137
+ function toRelativeModulePath({ fromDir, filePath }) {
138
+ return `./${node_path.posix.relative(fromDir, filePath)}`;
141
139
  }
142
140
  function isBarrelPath(path) {
143
141
  return path.endsWith(BARREL_SUFFIX);
144
142
  }
145
- function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
143
+ function makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }) {
146
144
  const names = /* @__PURE__ */ new Map();
147
145
  const uniqueExports = [];
148
146
  for (const item of exports) {
149
- const itemPath = toPosixPath((0, node_path.resolve)(dirPath, item.path));
147
+ const itemPath = node_path.posix.join(dirPath, item.path);
150
148
  const itemNames = Array.isArray(item.name) ? item.name : item.name ? [item.name] : sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name ? [source.name] : []);
151
149
  const uniqueNames = [];
152
150
  for (const name of itemNames ?? []) {
@@ -158,94 +156,100 @@ function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
158
156
  }
159
157
  const key = [
160
158
  name,
161
- toPosixPath((0, node_path.resolve)(dirPath, first)),
159
+ node_path.posix.join(dirPath, first),
162
160
  itemPath
163
161
  ].join("\0");
164
162
  if (reportedCollisions.has(key)) continue;
165
163
  reportedCollisions.add(key);
166
- _kubb_core.Diagnostics.report({
167
- code: _kubb_core.Diagnostics.code.barrelDuplicateExport,
164
+ _kubb_kit.Diagnostics.report({
165
+ code: _kubb_kit.Diagnostics.code.barrelDuplicateExport,
168
166
  severity: "error",
169
167
  message: `"${name}" is exported by both "${first}" and "${item.path}", so "${dirPath}${BARREL_SUFFIX}" cannot re-export both.`,
170
168
  help: "Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names."
171
169
  });
172
170
  }
173
- if (!itemNames || item.name == null || uniqueNames.length > 0) uniqueExports.push(item.name == null ? item : _kubb_ast.ast.factory.createExport({
171
+ if (!itemNames || item.name == null || uniqueNames.length > 0) uniqueExports.push(item.name == null ? item : _kubb_kit.ast.factory.createExport({
174
172
  ...item,
175
173
  name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0]
176
174
  }));
177
175
  }
178
- return _kubb_ast.ast.factory.createFile({
176
+ return _kubb_kit.ast.factory.createFile({
179
177
  baseName: "index.ts",
180
178
  path: `${dirPath}${BARREL_SUFFIX}`,
181
- exports: uniqueExports,
182
- sources: [],
183
- imports: [],
184
- banner: void 0,
185
- footer: void 0
179
+ exports: uniqueExports
186
180
  });
187
181
  }
188
- function hasOnlyNonIndexableSources(sources) {
189
- if (sources.length === 0) return false;
190
- for (const source of sources) if (source.isIndexable) return false;
191
- return true;
192
- }
193
- function partitionIndexableNames(sources) {
194
- const byTypeOnly = /* @__PURE__ */ new Map([[false, /* @__PURE__ */ new Set()], [true, /* @__PURE__ */ new Set()]]);
195
- for (const source of sources) {
196
- if (!source.isIndexable || !source.name) continue;
197
- byTypeOnly.get(Boolean(source.isTypeOnly)).add(source.name);
198
- }
199
- return byTypeOnly;
182
+ function indexableNames({ sources, isTypeOnly }) {
183
+ const names = sources.flatMap((source) => source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []);
184
+ return [...new Set(names)].sort();
200
185
  }
201
186
  const allStrategy = ({ dirPath, leafPath, sourceFile }) => {
202
- if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return [];
203
- return [_kubb_ast.ast.factory.createExport({ path: toRelativeModulePath(dirPath, leafPath) })];
187
+ const sources = sourceFile?.sources ?? [];
188
+ if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return [];
189
+ return [_kubb_kit.ast.factory.createExport({ path: toRelativeModulePath({
190
+ fromDir: dirPath,
191
+ filePath: leafPath
192
+ }) })];
204
193
  };
205
194
  const namedStrategy = ({ dirPath, leafPath, sourceFile }) => {
206
- const modulePath = toRelativeModulePath(dirPath, leafPath);
207
- if (!sourceFile) return [_kubb_ast.ast.factory.createExport({ path: modulePath })];
208
- const namesByTypeOnly = partitionIndexableNames(sourceFile.sources);
209
- const valueNames = namesByTypeOnly.get(false);
210
- const typeNames = namesByTypeOnly.get(true);
211
- if (valueNames.size === 0 && typeNames.size === 0) {
195
+ const modulePath = toRelativeModulePath({
196
+ fromDir: dirPath,
197
+ filePath: leafPath
198
+ });
199
+ if (!sourceFile) return [_kubb_kit.ast.factory.createExport({ path: modulePath })];
200
+ const valueNames = indexableNames({
201
+ sources: sourceFile.sources,
202
+ isTypeOnly: false
203
+ });
204
+ const typeNames = indexableNames({
205
+ sources: sourceFile.sources,
206
+ isTypeOnly: true
207
+ });
208
+ if (valueNames.length === 0 && typeNames.length === 0) {
212
209
  if (sourceFile.sources.length > 0) return [];
213
- return [_kubb_ast.ast.factory.createExport({ path: modulePath })];
210
+ return [_kubb_kit.ast.factory.createExport({ path: modulePath })];
214
211
  }
215
212
  const exports = [];
216
- if (valueNames.size > 0) exports.push(_kubb_ast.ast.factory.createExport({
217
- name: [...valueNames].sort(),
213
+ if (valueNames.length > 0) exports.push(_kubb_kit.ast.factory.createExport({
214
+ name: valueNames,
218
215
  path: modulePath
219
216
  }));
220
- if (typeNames.size > 0) exports.push(_kubb_ast.ast.factory.createExport({
221
- name: [...typeNames].sort(),
217
+ if (typeNames.length > 0) exports.push(_kubb_kit.ast.factory.createExport({
218
+ name: typeNames,
222
219
  path: modulePath,
223
220
  isTypeOnly: true
224
221
  }));
225
222
  return exports;
226
223
  };
227
- const LEAF_STRATEGIES = /* @__PURE__ */ new Map([["all", allStrategy], ["named", namedStrategy]]);
228
224
  /**
229
225
  * Post-order walk that yields a barrel per visited directory.
230
226
  * Returns the list of leaf file paths collected in this subtree (used by the parent call).
231
227
  */
232
- function* walkAllOrNamed(node, params, isRoot) {
228
+ function* walkAllOrNamed(node, params, { isRoot, recursive }) {
233
229
  const subtreeLeaves = [];
234
230
  for (const child of node.children) {
235
231
  if (child.isFile) {
236
232
  if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path);
237
233
  continue;
238
234
  }
239
- const childLeaves = yield* walkAllOrNamed(child, params, false);
235
+ const childLeaves = yield* walkAllOrNamed(child, params, {
236
+ isRoot: false,
237
+ recursive
238
+ });
240
239
  for (const leaf of childLeaves) subtreeLeaves.push(leaf);
241
240
  }
242
- if (!isRoot && !params.recursive) return subtreeLeaves;
241
+ if (!isRoot && !recursive) return subtreeLeaves;
243
242
  const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({
244
243
  dirPath: node.path,
245
244
  leafPath,
246
- sourceFile: params.sourceFiles.get(leafPath) ?? null
245
+ sourceFile: params.sourceFiles.get(leafPath)
247
246
  }));
248
- if (exports.length > 0) yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions);
247
+ if (exports.length > 0) yield makeBarrel({
248
+ dirPath: node.path,
249
+ exports,
250
+ sourceFiles: params.sourceFiles,
251
+ reportedCollisions: params.reportedCollisions
252
+ });
249
253
  return subtreeLeaves;
250
254
  }
251
255
  /**
@@ -260,48 +264,46 @@ function* walkNested(node, params) {
260
264
  for (const child of node.children) {
261
265
  if (child.isFile) {
262
266
  if (isBarrelPath(child.path)) continue;
263
- const sourceFile = params.sourceFiles.get(child.path) ?? null;
264
267
  exports.push(...params.strategy({
265
268
  dirPath: node.path,
266
269
  leafPath: child.path,
267
- sourceFile
270
+ sourceFile: params.sourceFiles.get(child.path)
268
271
  }));
269
272
  continue;
270
273
  }
271
- if (yield* walkNested(child, params)) exports.push(_kubb_ast.ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }));
274
+ if (yield* walkNested(child, params)) exports.push(_kubb_kit.ast.factory.createExport({ path: toRelativeModulePath({
275
+ fromDir: node.path,
276
+ filePath: `${child.path}${BARREL_SUFFIX}`
277
+ }) }));
272
278
  }
273
279
  if (exports.length > 0) {
274
- yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions);
280
+ yield makeBarrel({
281
+ dirPath: node.path,
282
+ exports,
283
+ sourceFiles: params.sourceFiles,
284
+ reportedCollisions: params.reportedCollisions
285
+ });
275
286
  return true;
276
287
  }
277
288
  return false;
278
289
  }
279
- function indexRelevantFiles(files, outputPath) {
290
+ /**
291
+ * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
292
+ * files under that path and builds their directory tree. Reuse the result across every barrel
293
+ * derived from the same root rather than re-filtering and re-building per barrel.
294
+ */
295
+ function buildBarrelIndex(outputPath, files) {
280
296
  const outputPrefix = `${toPosixPath(outputPath)}/`;
281
297
  const sourceFiles = /* @__PURE__ */ new Map();
282
- const paths = [];
283
298
  for (const file of files) {
284
299
  const normalized = toPosixPath(file.path);
285
300
  if (!normalized.startsWith(outputPrefix)) continue;
286
301
  if (isBarrelPath(normalized)) continue;
287
302
  if (!SOURCE_EXTENSIONS.has((0, node_path.extname)(normalized))) continue;
288
303
  sourceFiles.set(normalized, file);
289
- paths.push(normalized);
290
304
  }
291
305
  return {
292
- sourceFiles,
293
- paths
294
- };
295
- }
296
- /**
297
- * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
298
- * files under that path and builds their directory tree. Reuse the result across every barrel
299
- * derived from the same root rather than re-filtering and re-building per barrel.
300
- */
301
- function buildBarrelIndex(outputPath, files) {
302
- const { sourceFiles, paths } = indexRelevantFiles(files, outputPath);
303
- return {
304
- tree: buildTree(outputPath, paths),
306
+ tree: buildTree(outputPath, [...sourceFiles.keys()]),
305
307
  sourceFiles
306
308
  };
307
309
  }
@@ -315,7 +317,7 @@ function findNode(node, targetPath) {
315
317
  for (const child of node.children) if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) return findNode(child, targetPath);
316
318
  }
317
319
  /**
318
- * Yields barrel `FileNode`s for `targetPath` (or the index root), derived from a shared index.
320
+ * Yields barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.
319
321
  * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per
320
322
  * plugin, plus the root) from one index avoids re-scanning the full file set for each.
321
323
  *
@@ -330,8 +332,7 @@ function findNode(node, targetPath) {
330
332
  function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
331
333
  const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree;
332
334
  if (!node) return;
333
- const strategy = LEAF_STRATEGIES.get(barrelType);
334
- if (!strategy) return;
335
+ const strategy = barrelType === "named" ? namedStrategy : allStrategy;
335
336
  if (nested) {
336
337
  yield* walkNested(node, {
337
338
  sourceFiles: index.sourceFiles,
@@ -343,31 +344,11 @@ function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recurs
343
344
  yield* walkAllOrNamed(node, {
344
345
  sourceFiles: index.sourceFiles,
345
346
  strategy,
346
- recursive,
347
347
  reportedCollisions
348
- }, true);
349
- }
350
- /**
351
- * Builds a POSIX-normalized prefix for a plugin's output. A directory output gets a trailing `/`,
352
- * while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.
353
- *
354
- * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
355
- */
356
- function getPluginOutputPrefix(plugin, config) {
357
- const resolved = toPosixPath((0, node_path.resolve)(config.root, config.output.path, plugin.options.output.path));
358
- return plugin.options.output.mode === "file" ? resolved : `${resolved}/`;
359
- }
360
- /**
361
- * Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a
362
- * trailing `/` matches a directory subtree, and a prefix without one matches that exact file
363
- * (used for `mode: 'file'` outputs).
364
- *
365
- * Both sides are POSIX-normalized so Windows backslash paths match correctly.
366
- */
367
- function isExcludedPath(filePath, prefixes) {
368
- const normalized = toPosixPath(filePath);
369
- for (const prefix of prefixes) if (prefix.endsWith("/") ? normalized.startsWith(prefix) : normalized === prefix) return true;
370
- return false;
348
+ }, {
349
+ isRoot: true,
350
+ recursive
351
+ });
371
352
  }
372
353
  //#endregion
373
354
  //#region src/plugin.ts
@@ -381,10 +362,9 @@ function isExcludedPath(filePath, prefixes) {
381
362
  function withBarrelBannerFooter({ file, plugin, config }) {
382
363
  const output = plugin.options?.output;
383
364
  const resolver = plugin.resolver;
384
- if (!resolver) return file;
385
365
  const hasBanner = output?.banner !== void 0;
386
366
  const hasFooter = output?.footer !== void 0;
387
- if (!hasBanner && !hasFooter) return file;
367
+ if (!resolver || !hasBanner && !hasFooter) return file;
388
368
  const context = {
389
369
  output,
390
370
  config,
@@ -438,8 +418,8 @@ const pluginBarrelName = "plugin-barrel";
438
418
  * })
439
419
  * ```
440
420
  */
441
- const pluginBarrel = (0, _kubb_core.definePlugin)(() => {
442
- const excludedPrefixes = /* @__PURE__ */ new Set();
421
+ const pluginBarrel = (0, _kubb_kit.definePlugin)(() => {
422
+ const excludedTargets = [];
443
423
  const pendingBarrels = [];
444
424
  return {
445
425
  name: pluginBarrelName,
@@ -447,24 +427,21 @@ const pluginBarrel = (0, _kubb_core.definePlugin)(() => {
447
427
  hooks: {
448
428
  "kubb:plugin:end"({ plugin, config }) {
449
429
  if (plugin.name === "plugin-barrel") return;
450
- const pluginBarrelOpt = plugin.options.output?.barrel;
451
- const configBarrel = config.output.barrel;
452
- const barrelConfig = (() => {
453
- if (pluginBarrelOpt !== void 0) return pluginBarrelOpt;
454
- if (configBarrel !== void 0) return configBarrel === false ? false : {
455
- ...configBarrel,
456
- nested: false
457
- };
458
- return false;
459
- })();
430
+ const barrelConfig = plugin.options.output?.barrel ?? config.output.barrel ?? false;
431
+ const base = node_path.default.resolve(config.root, config.output.path);
432
+ const target = node_path.default.resolve(base, plugin.options.output.path);
460
433
  if (barrelConfig === false) {
461
- excludedPrefixes.add(getPluginOutputPrefix(plugin, config));
434
+ excludedTargets.push(target);
462
435
  return;
463
436
  }
464
437
  if (plugin.options.output.mode === "file") return;
465
- const base = node_path.default.resolve(config.root, config.output.path);
466
- const target = node_path.default.resolve(base, plugin.options.output.path);
467
- if (!isPathInside(target, base)) throw new Error("Invalid output path");
438
+ if (!isPathInside(target, base)) throw new _kubb_kit.Diagnostics.Error({
439
+ code: _kubb_kit.Diagnostics.code.pathTraversal,
440
+ severity: "error",
441
+ message: `Output path "${plugin.options.output.path}" of plugin "${plugin.name}" resolves outside the output directory "${base}".`,
442
+ help: "Keep the plugin `output.path` within `config.output.path`.",
443
+ location: { kind: "config" }
444
+ });
468
445
  pendingBarrels.push({
469
446
  plugin,
470
447
  target,
@@ -475,8 +452,8 @@ const pluginBarrel = (0, _kubb_core.definePlugin)(() => {
475
452
  "kubb:plugins:end"({ files, config, upsertFile }) {
476
453
  const rootBarrelConfig = config.output.barrel ?? false;
477
454
  const outputPath = node_path.default.resolve(config.root, config.output.path);
478
- const relevantFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes));
479
- excludedPrefixes.clear();
455
+ const relevantFiles = files.filter((file) => !excludedTargets.some((target) => isPathInside(file.path, target)));
456
+ excludedTargets.length = 0;
480
457
  const index = buildBarrelIndex(outputPath, relevantFiles);
481
458
  const reportedCollisions = /* @__PURE__ */ new Set();
482
459
  for (const { plugin, target, barrelType, nested } of pendingBarrels) for (const file of getBarrelFiles({
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["resolve","relative","isAbsolute","resolve","Diagnostics","ast","extname","definePlugin","path"],"sources":["../../../internals/utils/src/fs.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\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 * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\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, resolve } from 'node:path'\nimport { ast, type ExportNode, type FileNode, type SourceNode } from '@kubb/ast'\nimport { Diagnostics } from '@kubb/core'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\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 node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\ntype BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // WeakMap keyed by object identity so directory nodes are GC-eligible once the tree is discarded.\n const childIndex = new WeakMap<BuildTree, Map<string, BuildTree>>()\n childIndex.set(root, new Map())\n\n const rootPrefix = `${normalizedRoot}/`\n\n for (const filePath of filePaths) {\n const normalized = toPosixPath(filePath)\n if (!normalized.startsWith(rootPrefix)) continue\n\n const parts = normalized.slice(rootPrefix.length).split('/')\n if (parts.length === 0) continue\n\n let current = root\n const lastIndex = parts.length - 1\n for (const [i, part] of parts.entries()) {\n if (!part) continue\n\n const isLast = i === lastIndex\n const siblings = childIndex.get(current)!\n let child = siblings.get(part)\n if (!child) {\n child = { path: `${current.path}/${part}`, children: [], isFile: isLast }\n current.children.push(child)\n siblings.set(part, child)\n if (!isLast) childIndex.set(child, new Map())\n }\n current = child\n }\n }\n\n sortTree(root)\n\n return root\n}\n\nfunction sortTree(node: BuildTree): void {\n if (node.children.length === 0) return\n node.children.sort(compareByPath)\n\n for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>, sourceFiles: ReadonlyMap<string, FileNode>, reportedCollisions: Set<string>): FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ExportNode> = []\n\n for (const item of exports) {\n const itemPath = toPosixPath(resolve(dirPath, item.path))\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, toPosixPath(resolve(dirPath, first)), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\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\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports: uniqueExports,\n sources: [],\n imports: [],\n // Default to no banner/footer. The barrel plugin resolves a configured plugin\n // banner/footer (with isBarrel: true) afterwards, so a `banner` function can\n // decide per file whether a barrel should carry a directive like \"use server\".\n banner: undefined,\n footer: undefined,\n })\n}\n\ntype LeafContext = {\n dirPath: string\n leafPath: string\n sourceFile: FileNode | null\n}\n\ntype LeafStrategy = (ctx: LeafContext) => Array<ExportNode>\n\nfunction hasOnlyNonIndexableSources(sources: ReadonlyArray<SourceNode>): boolean {\n if (sources.length === 0) return false\n for (const source of sources) {\n if (source.isIndexable) return false\n }\n return true\n}\n\nfunction partitionIndexableNames(sources: ReadonlyArray<SourceNode>): Map<boolean, Set<string>> {\n const byTypeOnly = new Map<boolean, Set<string>>([\n [false, new Set()],\n [true, new Set()],\n ])\n for (const source of sources) {\n if (!source.isIndexable || !source.name) continue\n byTypeOnly.get(Boolean(source.isTypeOnly))!.add(source.name)\n }\n return byTypeOnly\n}\n\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return []\n return [ast.factory.createExport({ path: toRelativeModulePath(dirPath, leafPath) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath(dirPath, leafPath)\n\n if (!sourceFile) return [ast.factory.createExport({ path: modulePath })]\n\n const namesByTypeOnly = partitionIndexableNames(sourceFile.sources)\n const valueNames = namesByTypeOnly.get(false)!\n const typeNames = namesByTypeOnly.get(true)!\n\n if (valueNames.size === 0 && typeNames.size === 0) {\n if (sourceFile.sources.length > 0) return []\n return [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(ast.factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(ast.factory.createExport({ name: [...typeNames].sort(), path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\nconst LEAF_STRATEGIES: ReadonlyMap<BarrelType, LeafStrategy> = new Map([\n ['all', allStrategy],\n ['named', namedStrategy],\n])\n\ntype LeafWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n recursive: boolean\n reportedCollisions: Set<string>\n}\n\n/**\n * Post-order walk that yields a barrel per visited directory.\n * Returns the list of leaf file paths collected in this subtree (used by the parent call).\n */\nfunction* walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean): Generator<FileNode, Array<string>> {\n const subtreeLeaves: Array<string> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)\n continue\n }\n\n const childLeaves = yield* walkAllOrNamed(child, params, false)\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !params.recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) ?? null }))\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions)\n }\n\n return subtreeLeaves\n}\n\ntype NestedWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\n}\n\n/**\n * Recursive walk that yields 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. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: NestedWalkParams): Generator<FileNode, boolean> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n const sourceFile = params.sourceFiles.get(child.path) ?? null\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions)\n return true\n }\n\n return false\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\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: BuildTree\n sourceFiles: ReadonlyMap<string, 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<FileNode>): BarrelIndex {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n return { tree: buildTree(outputPath, paths), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n nested?: boolean\n /**\n * Also generate a barrel for each sub-directory when nested is false.\n * No effect when nested is true (always generates hierarchical structure).\n */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields barrel `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 * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, recursive, reportedCollisions }, true)\n}\n\n/**\n * Builds a POSIX-normalized prefix for a plugin's output. A directory output gets a trailing `/`,\n * while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.\n *\n * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.\n */\nexport function getPluginOutputPrefix(plugin: NormalizedPlugin, config: Config): string {\n const resolved = toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))\n return plugin.options.output.mode === 'file' ? resolved : `${resolved}/`\n}\n\n/**\n * Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a\n * trailing `/` matches a directory subtree, and a prefix without one matches that exact file\n * (used for `mode: 'file'` outputs).\n *\n * Both sides are POSIX-normalized so Windows backslash paths match correctly.\n */\nexport function isExcludedPath(filePath: string, prefixes: ReadonlySet<string>): boolean {\n const normalized = toPosixPath(filePath)\n // Plain `for...of` over the Set rather than `.values().some()`: the iterator-helper `some`\n // allocates an iterator object per call, and this runs once per file during barrel generation.\n for (const prefix of prefixes) {\n const matched = prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix\n if (matched) return true\n }\n return false\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles, getPluginOutputPrefix, isExcludedPath } 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: FileNode; plugin: NormalizedPlugin; config: Config }): FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n if (!resolver) return file\n\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!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\ndeclare global {\n namespace Kubb {\n interface PluginOptionsRegistry {\n output: {\n /**\n * Barrel configuration for this plugin's output.\n * Set to `{ type: 'named' | 'all' }` to opt this plugin into a barrel. Set to `false`\n * (the default) to disable barrel generation for this plugin entirely, which also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default false\n */\n barrel?: PluginBarrelConfig | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Barrel configuration for the root barrel file at `config.output.path/index.ts`.\n * Set to `{ type: 'named' | 'all' }` to opt into a root barrel. Individual plugins can\n * override this via their own `output.barrel`.\n *\n * @default false\n */\n barrel?: BarrelConfig | false\n }\n }\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\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 = definePlugin(() => {\n const excludedPrefixes = new Set<string>()\n const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\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 pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\n\n // Root config barrel doesn't have nested, so we add it\n const barrelConfig: PluginBarrelConfig | false = (() => {\n if (pluginBarrelOpt !== undefined) return pluginBarrelOpt\n if (configBarrel !== undefined) return configBarrel === false ? false : { ...configBarrel, nested: false }\n return false\n })()\n\n if (barrelConfig === false) {\n excludedPrefixes.add(getPluginOutputPrefix(plugin, config))\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 const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n if (!isPathInside(target, base)) {\n throw new Error('Invalid output path')\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 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 = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["resolve","relative","isAbsolute","posix","Diagnostics","ast","extname","definePlugin","path","Diagnostics"],"sources":["../../../internals/utils/src/fs.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\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 * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\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 node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\ntype BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nfunction buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // WeakMap keyed by object identity so directory nodes are GC-eligible once the tree is discarded.\n const childIndex = new WeakMap<BuildTree, Map<string, BuildTree>>()\n childIndex.set(root, new Map())\n\n const rootPrefix = `${normalizedRoot}/`\n\n for (const filePath of filePaths) {\n const normalized = toPosixPath(filePath)\n if (!normalized.startsWith(rootPrefix)) continue\n\n const parts = normalized.slice(rootPrefix.length).split('/')\n let current = root\n const lastIndex = parts.length - 1\n for (const [i, part] of parts.entries()) {\n if (!part) continue\n\n const isLast = i === lastIndex\n const siblings = childIndex.get(current)!\n let child = siblings.get(part)\n if (!child) {\n child = { path: `${current.path}/${part}`, children: [], isFile: isLast }\n current.children.push(child)\n siblings.set(part, child)\n if (!isLast) childIndex.set(child, new Map())\n }\n current = child\n }\n }\n\n sortTree(root)\n\n return root\n}\n\nfunction sortTree(node: BuildTree): void {\n if (node.children.length === 0) return\n node.children.sort(compareByPath)\n\n for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n\nfunction toRelativeModulePath({ fromDir, filePath }: { fromDir: string; filePath: string }): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\ntype MakeBarrelParams = {\n dirPath: string\n exports: Array<ast.ExportNode>\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n reportedCollisions: Set<string>\n}\n\nfunction makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }: MakeBarrelParams): ast.FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ast.ExportNode> = []\n\n for (const item of exports) {\n const itemPath = posix.join(dirPath, item.path)\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\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\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n}\n\ntype LeafStrategy = (params: { dirPath: string; leafPath: string; sourceFile: ast.FileNode | undefined }) => Array<ast.ExportNode>\n\nfunction indexableNames({ sources, isTypeOnly }: { 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\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const sources = sourceFile?.sources ?? []\n if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return []\n return [ast.factory.createExport({ path: toRelativeModulePath({ fromDir: dirPath, filePath: leafPath }) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath({ fromDir: dirPath, filePath: leafPath })\n\n if (!sourceFile) return [ast.factory.createExport({ path: modulePath })]\n\n const valueNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: false })\n const typeNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: true })\n\n if (valueNames.length === 0 && typeNames.length === 0) {\n if (sourceFile.sources.length > 0) return []\n return [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ast.ExportNode> = []\n if (valueNames.length > 0) {\n exports.push(ast.factory.createExport({ name: valueNames, path: modulePath }))\n }\n if (typeNames.length > 0) {\n exports.push(ast.factory.createExport({ name: typeNames, path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\ntype WalkParams = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\n}\n\n/**\n * Post-order walk that yields a barrel per visited directory.\n * Returns the list of leaf file paths collected in this subtree (used by the parent call).\n */\nfunction* walkAllOrNamed(\n node: BuildTree,\n params: WalkParams,\n { isRoot, recursive }: { isRoot: boolean; recursive: boolean },\n): Generator<ast.FileNode, Array<string>> {\n const subtreeLeaves: Array<string> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)\n continue\n }\n\n const childLeaves = yield* walkAllOrNamed(child, params, { isRoot: false, recursive })\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\n }\n\n return subtreeLeaves\n}\n\n/**\n * Recursive walk that yields 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. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: WalkParams): Generator<ast.FileNode, boolean> {\n const exports: Array<ast.ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile: params.sourceFiles.get(child.path) }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath({ fromDir: node.path, filePath: `${child.path}${BARREL_SUFFIX}` }) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\n return true\n }\n\n return false\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: BuildTree\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 outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, ast.FileNode>()\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n }\n\n return { tree: buildTree(outputPath, [...sourceFiles.keys()]), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n nested?: boolean\n /**\n * Also generate a barrel for each sub-directory when nested is false.\n * No effect when nested is true (always generates hierarchical structure).\n */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields 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 * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<ast.FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions }, { isRoot: true, recursive })\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport { definePlugin, Diagnostics } from '@kubb/kit'\nimport type { ast, Config, NormalizedPlugin, Plugin } from '@kubb/kit'\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\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 = definePlugin(() => {\n const excludedTargets: Array<string> = []\n const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\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 // 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 excludedTargets.push(target)\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 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) => !excludedTargets.some((target) => isPathInside(file.path, target)))\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,31 +1,54 @@
1
1
  import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
- import { Plugin } from "@kubb/core";
3
- //#region src/types.d.ts
2
+ import { Plugin } from "@kubb/kit";
3
+ //#region src/plugin.d.ts
4
4
  /**
5
- * Barrel export strategy.
6
- *
7
- * - `'all'` generates `export * from '...'` for every file
8
- * - `'named'` generates `export { name1, name2 } from '...'` using each file's named exports
5
+ * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups
6
+ * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.
9
7
  */
10
- type BarrelType = 'all' | 'named';
8
+ export declare const pluginBarrelName = "plugin-barrel";
11
9
  /**
12
- * Barrel configuration at the root config level.
10
+ * Generates an `index.ts` for every plugin output directory and one root
11
+ * barrel at `config.output.path/index.ts` after the build completes. Ships
12
+ * with Kubb and is registered by default in `defineConfig`, but generates
13
+ * nothing until a barrel is configured.
14
+ *
15
+ * Each plugin inherits `output.barrel` from `config.output.barrel` (which
16
+ * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on
17
+ * the root config, a plugin, or both to opt in; a plugin-level `false`
18
+ * overrides an enabled root barrel and also excludes that plugin's files
19
+ * from the root barrel.
20
+ *
21
+ * A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output
22
+ * is a single file. The root barrel re-exports that file directly.
13
23
  *
14
24
  * @example
15
25
  * ```ts
16
- * barrel: { type: 'named' }
17
- * barrel: { type: 'all' }
18
- * barrel: false // no barrel generated (default)
26
+ * import { defineConfig } from '@kubb/core'
27
+ * import { pluginBarrel } from '@kubb/plugin-barrel'
28
+ * import { pluginTs } from '@kubb/plugin-ts'
29
+ * import { pluginZod } from '@kubb/plugin-zod'
30
+ *
31
+ * export default defineConfig({
32
+ * input: './petStore.yaml',
33
+ * output: { path: 'src/gen', barrel: { type: 'named' } },
34
+ * plugins: [
35
+ * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
36
+ * pluginZod({ output: { path: 'schemas' } }),
37
+ * pluginBarrel(),
38
+ * ],
39
+ * })
19
40
  * ```
20
41
  */
21
- type BarrelConfig = {
22
- /**
23
- * Export strategy for the root barrel file.
24
- * - `'all'` wildcard exports: `export * from './file'`
25
- * - `'named'` explicit exports: `export { x, y } from './file'`
26
- */
27
- type: BarrelType;
28
- };
42
+ export declare const pluginBarrel: (options?: object | undefined) => Plugin<import("@kubb/core").PluginFactoryOptions<string, object, object, import("@kubb/core").Resolver>>;
43
+ //#endregion
44
+ //#region src/types.d.ts
45
+ /**
46
+ * Barrel export strategy.
47
+ *
48
+ * - `'all'` generates `export * from '...'` for every file
49
+ * - `'named'` generates `export { name1, name2 } from '...'` using each file's named exports
50
+ */
51
+ type BarrelType = 'all' | 'named';
29
52
  /**
30
53
  * Barrel configuration at the plugin level.
31
54
  * Supports nested barrel generation in subdirectories.
@@ -51,8 +74,10 @@ type PluginBarrelConfig = {
51
74
  */
52
75
  nested?: boolean;
53
76
  };
54
- //#endregion
55
- //#region src/plugin.d.ts
77
+ /**
78
+ * Barrel configuration at the root config level: the export strategy only, since the root barrel is never nested.
79
+ */
80
+ type BarrelConfig = Pick<PluginBarrelConfig, 'type'>;
56
81
  declare global {
57
82
  namespace Kubb {
58
83
  interface PluginOptionsRegistry {
@@ -84,45 +109,6 @@ declare global {
84
109
  }
85
110
  }
86
111
  }
87
- /**
88
- * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups
89
- * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.
90
- */
91
- export declare const pluginBarrelName = "plugin-barrel";
92
- /**
93
- * Generates an `index.ts` for every plugin output directory and one root
94
- * barrel at `config.output.path/index.ts` after the build completes. Ships
95
- * with Kubb and is registered by default in `defineConfig`, but generates
96
- * nothing until a barrel is configured.
97
- *
98
- * Each plugin inherits `output.barrel` from `config.output.barrel` (which
99
- * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on
100
- * the root config, a plugin, or both to opt in; a plugin-level `false`
101
- * overrides an enabled root barrel and also excludes that plugin's files
102
- * from the root barrel.
103
- *
104
- * A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output
105
- * is a single file. The root barrel re-exports that file directly.
106
- *
107
- * @example
108
- * ```ts
109
- * import { defineConfig } from '@kubb/core'
110
- * import { pluginBarrel } from '@kubb/plugin-barrel'
111
- * import { pluginTs } from '@kubb/plugin-ts'
112
- * import { pluginZod } from '@kubb/plugin-zod'
113
- *
114
- * export default defineConfig({
115
- * input: './petStore.yaml',
116
- * output: { path: 'src/gen', barrel: { type: 'named' } },
117
- * plugins: [
118
- * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
119
- * pluginZod({ output: { path: 'schemas' } }),
120
- * pluginBarrel(),
121
- * ],
122
- * })
123
- * ```
124
- */
125
- export declare const pluginBarrel: (options?: object | undefined) => Plugin<import("@kubb/core").PluginFactoryOptions<string, object, object, import("@kubb/core").Resolver>>;
126
112
  //#endregion
127
113
  export type { BarrelConfig, BarrelType, PluginBarrelConfig };
128
114
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import path, { extname, isAbsolute, relative, resolve } from "node:path";
3
- import { Diagnostics, definePlugin } from "@kubb/core";
4
- import { ast } from "@kubb/ast";
2
+ import path, { extname, isAbsolute, posix, relative, resolve } from "node:path";
3
+ import { Diagnostics, ast, definePlugin } from "@kubb/kit";
5
4
  //#region ../../internals/utils/src/fs.ts
6
5
  /**
7
6
  * Converts a filesystem path to use POSIX (`/`) separators.
@@ -81,7 +80,6 @@ function buildTree(rootPath, filePaths) {
81
80
  const normalized = toPosixPath(filePath);
82
81
  if (!normalized.startsWith(rootPrefix)) continue;
83
82
  const parts = normalized.slice(rootPrefix.length).split("/");
84
- if (parts.length === 0) continue;
85
83
  let current = root;
86
84
  const lastIndex = parts.length - 1;
87
85
  for (const [i, part] of parts.entries()) {
@@ -113,17 +111,17 @@ function sortTree(node) {
113
111
  function compareByPath(a, b) {
114
112
  return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
115
113
  }
116
- function toRelativeModulePath(fromDir, filePath) {
117
- return `./${filePath.slice(fromDir.length + 1)}`;
114
+ function toRelativeModulePath({ fromDir, filePath }) {
115
+ return `./${posix.relative(fromDir, filePath)}`;
118
116
  }
119
117
  function isBarrelPath(path) {
120
118
  return path.endsWith(BARREL_SUFFIX);
121
119
  }
122
- function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
120
+ function makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }) {
123
121
  const names = /* @__PURE__ */ new Map();
124
122
  const uniqueExports = [];
125
123
  for (const item of exports) {
126
- const itemPath = toPosixPath(resolve(dirPath, item.path));
124
+ const itemPath = posix.join(dirPath, item.path);
127
125
  const itemNames = Array.isArray(item.name) ? item.name : item.name ? [item.name] : sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name ? [source.name] : []);
128
126
  const uniqueNames = [];
129
127
  for (const name of itemNames ?? []) {
@@ -135,7 +133,7 @@ function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
135
133
  }
136
134
  const key = [
137
135
  name,
138
- toPosixPath(resolve(dirPath, first)),
136
+ posix.join(dirPath, first),
139
137
  itemPath
140
138
  ].join("\0");
141
139
  if (reportedCollisions.has(key)) continue;
@@ -155,74 +153,80 @@ function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
155
153
  return ast.factory.createFile({
156
154
  baseName: "index.ts",
157
155
  path: `${dirPath}${BARREL_SUFFIX}`,
158
- exports: uniqueExports,
159
- sources: [],
160
- imports: [],
161
- banner: void 0,
162
- footer: void 0
156
+ exports: uniqueExports
163
157
  });
164
158
  }
165
- function hasOnlyNonIndexableSources(sources) {
166
- if (sources.length === 0) return false;
167
- for (const source of sources) if (source.isIndexable) return false;
168
- return true;
169
- }
170
- function partitionIndexableNames(sources) {
171
- const byTypeOnly = /* @__PURE__ */ new Map([[false, /* @__PURE__ */ new Set()], [true, /* @__PURE__ */ new Set()]]);
172
- for (const source of sources) {
173
- if (!source.isIndexable || !source.name) continue;
174
- byTypeOnly.get(Boolean(source.isTypeOnly)).add(source.name);
175
- }
176
- return byTypeOnly;
159
+ function indexableNames({ sources, isTypeOnly }) {
160
+ const names = sources.flatMap((source) => source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []);
161
+ return [...new Set(names)].sort();
177
162
  }
178
163
  const allStrategy = ({ dirPath, leafPath, sourceFile }) => {
179
- if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return [];
180
- return [ast.factory.createExport({ path: toRelativeModulePath(dirPath, leafPath) })];
164
+ const sources = sourceFile?.sources ?? [];
165
+ if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return [];
166
+ return [ast.factory.createExport({ path: toRelativeModulePath({
167
+ fromDir: dirPath,
168
+ filePath: leafPath
169
+ }) })];
181
170
  };
182
171
  const namedStrategy = ({ dirPath, leafPath, sourceFile }) => {
183
- const modulePath = toRelativeModulePath(dirPath, leafPath);
172
+ const modulePath = toRelativeModulePath({
173
+ fromDir: dirPath,
174
+ filePath: leafPath
175
+ });
184
176
  if (!sourceFile) return [ast.factory.createExport({ path: modulePath })];
185
- const namesByTypeOnly = partitionIndexableNames(sourceFile.sources);
186
- const valueNames = namesByTypeOnly.get(false);
187
- const typeNames = namesByTypeOnly.get(true);
188
- if (valueNames.size === 0 && typeNames.size === 0) {
177
+ const valueNames = indexableNames({
178
+ sources: sourceFile.sources,
179
+ isTypeOnly: false
180
+ });
181
+ const typeNames = indexableNames({
182
+ sources: sourceFile.sources,
183
+ isTypeOnly: true
184
+ });
185
+ if (valueNames.length === 0 && typeNames.length === 0) {
189
186
  if (sourceFile.sources.length > 0) return [];
190
187
  return [ast.factory.createExport({ path: modulePath })];
191
188
  }
192
189
  const exports = [];
193
- if (valueNames.size > 0) exports.push(ast.factory.createExport({
194
- name: [...valueNames].sort(),
190
+ if (valueNames.length > 0) exports.push(ast.factory.createExport({
191
+ name: valueNames,
195
192
  path: modulePath
196
193
  }));
197
- if (typeNames.size > 0) exports.push(ast.factory.createExport({
198
- name: [...typeNames].sort(),
194
+ if (typeNames.length > 0) exports.push(ast.factory.createExport({
195
+ name: typeNames,
199
196
  path: modulePath,
200
197
  isTypeOnly: true
201
198
  }));
202
199
  return exports;
203
200
  };
204
- const LEAF_STRATEGIES = /* @__PURE__ */ new Map([["all", allStrategy], ["named", namedStrategy]]);
205
201
  /**
206
202
  * Post-order walk that yields a barrel per visited directory.
207
203
  * Returns the list of leaf file paths collected in this subtree (used by the parent call).
208
204
  */
209
- function* walkAllOrNamed(node, params, isRoot) {
205
+ function* walkAllOrNamed(node, params, { isRoot, recursive }) {
210
206
  const subtreeLeaves = [];
211
207
  for (const child of node.children) {
212
208
  if (child.isFile) {
213
209
  if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path);
214
210
  continue;
215
211
  }
216
- const childLeaves = yield* walkAllOrNamed(child, params, false);
212
+ const childLeaves = yield* walkAllOrNamed(child, params, {
213
+ isRoot: false,
214
+ recursive
215
+ });
217
216
  for (const leaf of childLeaves) subtreeLeaves.push(leaf);
218
217
  }
219
- if (!isRoot && !params.recursive) return subtreeLeaves;
218
+ if (!isRoot && !recursive) return subtreeLeaves;
220
219
  const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({
221
220
  dirPath: node.path,
222
221
  leafPath,
223
- sourceFile: params.sourceFiles.get(leafPath) ?? null
222
+ sourceFile: params.sourceFiles.get(leafPath)
224
223
  }));
225
- if (exports.length > 0) yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions);
224
+ if (exports.length > 0) yield makeBarrel({
225
+ dirPath: node.path,
226
+ exports,
227
+ sourceFiles: params.sourceFiles,
228
+ reportedCollisions: params.reportedCollisions
229
+ });
226
230
  return subtreeLeaves;
227
231
  }
228
232
  /**
@@ -237,48 +241,46 @@ function* walkNested(node, params) {
237
241
  for (const child of node.children) {
238
242
  if (child.isFile) {
239
243
  if (isBarrelPath(child.path)) continue;
240
- const sourceFile = params.sourceFiles.get(child.path) ?? null;
241
244
  exports.push(...params.strategy({
242
245
  dirPath: node.path,
243
246
  leafPath: child.path,
244
- sourceFile
247
+ sourceFile: params.sourceFiles.get(child.path)
245
248
  }));
246
249
  continue;
247
250
  }
248
- if (yield* walkNested(child, params)) exports.push(ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }));
251
+ if (yield* walkNested(child, params)) exports.push(ast.factory.createExport({ path: toRelativeModulePath({
252
+ fromDir: node.path,
253
+ filePath: `${child.path}${BARREL_SUFFIX}`
254
+ }) }));
249
255
  }
250
256
  if (exports.length > 0) {
251
- yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions);
257
+ yield makeBarrel({
258
+ dirPath: node.path,
259
+ exports,
260
+ sourceFiles: params.sourceFiles,
261
+ reportedCollisions: params.reportedCollisions
262
+ });
252
263
  return true;
253
264
  }
254
265
  return false;
255
266
  }
256
- function indexRelevantFiles(files, outputPath) {
267
+ /**
268
+ * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
269
+ * files under that path and builds their directory tree. Reuse the result across every barrel
270
+ * derived from the same root rather than re-filtering and re-building per barrel.
271
+ */
272
+ function buildBarrelIndex(outputPath, files) {
257
273
  const outputPrefix = `${toPosixPath(outputPath)}/`;
258
274
  const sourceFiles = /* @__PURE__ */ new Map();
259
- const paths = [];
260
275
  for (const file of files) {
261
276
  const normalized = toPosixPath(file.path);
262
277
  if (!normalized.startsWith(outputPrefix)) continue;
263
278
  if (isBarrelPath(normalized)) continue;
264
279
  if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue;
265
280
  sourceFiles.set(normalized, file);
266
- paths.push(normalized);
267
281
  }
268
282
  return {
269
- sourceFiles,
270
- paths
271
- };
272
- }
273
- /**
274
- * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
275
- * files under that path and builds their directory tree. Reuse the result across every barrel
276
- * derived from the same root rather than re-filtering and re-building per barrel.
277
- */
278
- function buildBarrelIndex(outputPath, files) {
279
- const { sourceFiles, paths } = indexRelevantFiles(files, outputPath);
280
- return {
281
- tree: buildTree(outputPath, paths),
283
+ tree: buildTree(outputPath, [...sourceFiles.keys()]),
282
284
  sourceFiles
283
285
  };
284
286
  }
@@ -292,7 +294,7 @@ function findNode(node, targetPath) {
292
294
  for (const child of node.children) if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) return findNode(child, targetPath);
293
295
  }
294
296
  /**
295
- * Yields barrel `FileNode`s for `targetPath` (or the index root), derived from a shared index.
297
+ * Yields barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.
296
298
  * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per
297
299
  * plugin, plus the root) from one index avoids re-scanning the full file set for each.
298
300
  *
@@ -307,8 +309,7 @@ function findNode(node, targetPath) {
307
309
  function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
308
310
  const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree;
309
311
  if (!node) return;
310
- const strategy = LEAF_STRATEGIES.get(barrelType);
311
- if (!strategy) return;
312
+ const strategy = barrelType === "named" ? namedStrategy : allStrategy;
312
313
  if (nested) {
313
314
  yield* walkNested(node, {
314
315
  sourceFiles: index.sourceFiles,
@@ -320,31 +321,11 @@ function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recurs
320
321
  yield* walkAllOrNamed(node, {
321
322
  sourceFiles: index.sourceFiles,
322
323
  strategy,
323
- recursive,
324
324
  reportedCollisions
325
- }, true);
326
- }
327
- /**
328
- * Builds a POSIX-normalized prefix for a plugin's output. A directory output gets a trailing `/`,
329
- * while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.
330
- *
331
- * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
332
- */
333
- function getPluginOutputPrefix(plugin, config) {
334
- const resolved = toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path));
335
- return plugin.options.output.mode === "file" ? resolved : `${resolved}/`;
336
- }
337
- /**
338
- * Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a
339
- * trailing `/` matches a directory subtree, and a prefix without one matches that exact file
340
- * (used for `mode: 'file'` outputs).
341
- *
342
- * Both sides are POSIX-normalized so Windows backslash paths match correctly.
343
- */
344
- function isExcludedPath(filePath, prefixes) {
345
- const normalized = toPosixPath(filePath);
346
- for (const prefix of prefixes) if (prefix.endsWith("/") ? normalized.startsWith(prefix) : normalized === prefix) return true;
347
- return false;
325
+ }, {
326
+ isRoot: true,
327
+ recursive
328
+ });
348
329
  }
349
330
  //#endregion
350
331
  //#region src/plugin.ts
@@ -358,10 +339,9 @@ function isExcludedPath(filePath, prefixes) {
358
339
  function withBarrelBannerFooter({ file, plugin, config }) {
359
340
  const output = plugin.options?.output;
360
341
  const resolver = plugin.resolver;
361
- if (!resolver) return file;
362
342
  const hasBanner = output?.banner !== void 0;
363
343
  const hasFooter = output?.footer !== void 0;
364
- if (!hasBanner && !hasFooter) return file;
344
+ if (!resolver || !hasBanner && !hasFooter) return file;
365
345
  const context = {
366
346
  output,
367
347
  config,
@@ -416,7 +396,7 @@ const pluginBarrelName = "plugin-barrel";
416
396
  * ```
417
397
  */
418
398
  const pluginBarrel = definePlugin(() => {
419
- const excludedPrefixes = /* @__PURE__ */ new Set();
399
+ const excludedTargets = [];
420
400
  const pendingBarrels = [];
421
401
  return {
422
402
  name: pluginBarrelName,
@@ -424,24 +404,21 @@ const pluginBarrel = definePlugin(() => {
424
404
  hooks: {
425
405
  "kubb:plugin:end"({ plugin, config }) {
426
406
  if (plugin.name === "plugin-barrel") return;
427
- const pluginBarrelOpt = plugin.options.output?.barrel;
428
- const configBarrel = config.output.barrel;
429
- const barrelConfig = (() => {
430
- if (pluginBarrelOpt !== void 0) return pluginBarrelOpt;
431
- if (configBarrel !== void 0) return configBarrel === false ? false : {
432
- ...configBarrel,
433
- nested: false
434
- };
435
- return false;
436
- })();
407
+ const barrelConfig = plugin.options.output?.barrel ?? config.output.barrel ?? false;
408
+ const base = path.resolve(config.root, config.output.path);
409
+ const target = path.resolve(base, plugin.options.output.path);
437
410
  if (barrelConfig === false) {
438
- excludedPrefixes.add(getPluginOutputPrefix(plugin, config));
411
+ excludedTargets.push(target);
439
412
  return;
440
413
  }
441
414
  if (plugin.options.output.mode === "file") return;
442
- const base = path.resolve(config.root, config.output.path);
443
- const target = path.resolve(base, plugin.options.output.path);
444
- if (!isPathInside(target, base)) throw new Error("Invalid output path");
415
+ if (!isPathInside(target, base)) throw new Diagnostics.Error({
416
+ code: Diagnostics.code.pathTraversal,
417
+ severity: "error",
418
+ message: `Output path "${plugin.options.output.path}" of plugin "${plugin.name}" resolves outside the output directory "${base}".`,
419
+ help: "Keep the plugin `output.path` within `config.output.path`.",
420
+ location: { kind: "config" }
421
+ });
445
422
  pendingBarrels.push({
446
423
  plugin,
447
424
  target,
@@ -452,8 +429,8 @@ const pluginBarrel = definePlugin(() => {
452
429
  "kubb:plugins:end"({ files, config, upsertFile }) {
453
430
  const rootBarrelConfig = config.output.barrel ?? false;
454
431
  const outputPath = path.resolve(config.root, config.output.path);
455
- const relevantFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes));
456
- excludedPrefixes.clear();
432
+ const relevantFiles = files.filter((file) => !excludedTargets.some((target) => isPathInside(file.path, target)));
433
+ excludedTargets.length = 0;
457
434
  const index = buildBarrelIndex(outputPath, relevantFiles);
458
435
  const reportedCollisions = /* @__PURE__ */ new Set();
459
436
  for (const { plugin, target, barrelType, nested } of pendingBarrels) for (const file of getBarrelFiles({
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/fs.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\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 * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\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, resolve } from 'node:path'\nimport { ast, type ExportNode, type FileNode, type SourceNode } from '@kubb/ast'\nimport { Diagnostics } from '@kubb/core'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\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 node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\ntype BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // WeakMap keyed by object identity so directory nodes are GC-eligible once the tree is discarded.\n const childIndex = new WeakMap<BuildTree, Map<string, BuildTree>>()\n childIndex.set(root, new Map())\n\n const rootPrefix = `${normalizedRoot}/`\n\n for (const filePath of filePaths) {\n const normalized = toPosixPath(filePath)\n if (!normalized.startsWith(rootPrefix)) continue\n\n const parts = normalized.slice(rootPrefix.length).split('/')\n if (parts.length === 0) continue\n\n let current = root\n const lastIndex = parts.length - 1\n for (const [i, part] of parts.entries()) {\n if (!part) continue\n\n const isLast = i === lastIndex\n const siblings = childIndex.get(current)!\n let child = siblings.get(part)\n if (!child) {\n child = { path: `${current.path}/${part}`, children: [], isFile: isLast }\n current.children.push(child)\n siblings.set(part, child)\n if (!isLast) childIndex.set(child, new Map())\n }\n current = child\n }\n }\n\n sortTree(root)\n\n return root\n}\n\nfunction sortTree(node: BuildTree): void {\n if (node.children.length === 0) return\n node.children.sort(compareByPath)\n\n for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>, sourceFiles: ReadonlyMap<string, FileNode>, reportedCollisions: Set<string>): FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ExportNode> = []\n\n for (const item of exports) {\n const itemPath = toPosixPath(resolve(dirPath, item.path))\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, toPosixPath(resolve(dirPath, first)), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\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\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports: uniqueExports,\n sources: [],\n imports: [],\n // Default to no banner/footer. The barrel plugin resolves a configured plugin\n // banner/footer (with isBarrel: true) afterwards, so a `banner` function can\n // decide per file whether a barrel should carry a directive like \"use server\".\n banner: undefined,\n footer: undefined,\n })\n}\n\ntype LeafContext = {\n dirPath: string\n leafPath: string\n sourceFile: FileNode | null\n}\n\ntype LeafStrategy = (ctx: LeafContext) => Array<ExportNode>\n\nfunction hasOnlyNonIndexableSources(sources: ReadonlyArray<SourceNode>): boolean {\n if (sources.length === 0) return false\n for (const source of sources) {\n if (source.isIndexable) return false\n }\n return true\n}\n\nfunction partitionIndexableNames(sources: ReadonlyArray<SourceNode>): Map<boolean, Set<string>> {\n const byTypeOnly = new Map<boolean, Set<string>>([\n [false, new Set()],\n [true, new Set()],\n ])\n for (const source of sources) {\n if (!source.isIndexable || !source.name) continue\n byTypeOnly.get(Boolean(source.isTypeOnly))!.add(source.name)\n }\n return byTypeOnly\n}\n\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return []\n return [ast.factory.createExport({ path: toRelativeModulePath(dirPath, leafPath) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath(dirPath, leafPath)\n\n if (!sourceFile) return [ast.factory.createExport({ path: modulePath })]\n\n const namesByTypeOnly = partitionIndexableNames(sourceFile.sources)\n const valueNames = namesByTypeOnly.get(false)!\n const typeNames = namesByTypeOnly.get(true)!\n\n if (valueNames.size === 0 && typeNames.size === 0) {\n if (sourceFile.sources.length > 0) return []\n return [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(ast.factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(ast.factory.createExport({ name: [...typeNames].sort(), path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\nconst LEAF_STRATEGIES: ReadonlyMap<BarrelType, LeafStrategy> = new Map([\n ['all', allStrategy],\n ['named', namedStrategy],\n])\n\ntype LeafWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n recursive: boolean\n reportedCollisions: Set<string>\n}\n\n/**\n * Post-order walk that yields a barrel per visited directory.\n * Returns the list of leaf file paths collected in this subtree (used by the parent call).\n */\nfunction* walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean): Generator<FileNode, Array<string>> {\n const subtreeLeaves: Array<string> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)\n continue\n }\n\n const childLeaves = yield* walkAllOrNamed(child, params, false)\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !params.recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) ?? null }))\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions)\n }\n\n return subtreeLeaves\n}\n\ntype NestedWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\n}\n\n/**\n * Recursive walk that yields 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. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: NestedWalkParams): Generator<FileNode, boolean> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n const sourceFile = params.sourceFiles.get(child.path) ?? null\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions)\n return true\n }\n\n return false\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\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: BuildTree\n sourceFiles: ReadonlyMap<string, 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<FileNode>): BarrelIndex {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n return { tree: buildTree(outputPath, paths), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n nested?: boolean\n /**\n * Also generate a barrel for each sub-directory when nested is false.\n * No effect when nested is true (always generates hierarchical structure).\n */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields barrel `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 * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, recursive, reportedCollisions }, true)\n}\n\n/**\n * Builds a POSIX-normalized prefix for a plugin's output. A directory output gets a trailing `/`,\n * while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.\n *\n * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.\n */\nexport function getPluginOutputPrefix(plugin: NormalizedPlugin, config: Config): string {\n const resolved = toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))\n return plugin.options.output.mode === 'file' ? resolved : `${resolved}/`\n}\n\n/**\n * Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a\n * trailing `/` matches a directory subtree, and a prefix without one matches that exact file\n * (used for `mode: 'file'` outputs).\n *\n * Both sides are POSIX-normalized so Windows backslash paths match correctly.\n */\nexport function isExcludedPath(filePath: string, prefixes: ReadonlySet<string>): boolean {\n const normalized = toPosixPath(filePath)\n // Plain `for...of` over the Set rather than `.values().some()`: the iterator-helper `some`\n // allocates an iterator object per call, and this runs once per file during barrel generation.\n for (const prefix of prefixes) {\n const matched = prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix\n if (matched) return true\n }\n return false\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles, getPluginOutputPrefix, isExcludedPath } 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: FileNode; plugin: NormalizedPlugin; config: Config }): FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n if (!resolver) return file\n\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!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\ndeclare global {\n namespace Kubb {\n interface PluginOptionsRegistry {\n output: {\n /**\n * Barrel configuration for this plugin's output.\n * Set to `{ type: 'named' | 'all' }` to opt this plugin into a barrel. Set to `false`\n * (the default) to disable barrel generation for this plugin entirely, which also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default false\n */\n barrel?: PluginBarrelConfig | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Barrel configuration for the root barrel file at `config.output.path/index.ts`.\n * Set to `{ type: 'named' | 'all' }` to opt into a root barrel. Individual plugins can\n * override this via their own `output.barrel`.\n *\n * @default false\n */\n barrel?: BarrelConfig | false\n }\n }\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\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 = definePlugin(() => {\n const excludedPrefixes = new Set<string>()\n const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\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 pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\n\n // Root config barrel doesn't have nested, so we add it\n const barrelConfig: PluginBarrelConfig | false = (() => {\n if (pluginBarrelOpt !== undefined) return pluginBarrelOpt\n if (configBarrel !== undefined) return configBarrel === false ? false : { ...configBarrel, nested: false }\n return false\n })()\n\n if (barrelConfig === false) {\n excludedPrefixes.add(getPluginOutputPrefix(plugin, config))\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 const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n if (!isPathInside(target, base)) {\n throw new Error('Invalid output path')\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 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 = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/fs.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\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 * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\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 node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\ntype BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nfunction buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // WeakMap keyed by object identity so directory nodes are GC-eligible once the tree is discarded.\n const childIndex = new WeakMap<BuildTree, Map<string, BuildTree>>()\n childIndex.set(root, new Map())\n\n const rootPrefix = `${normalizedRoot}/`\n\n for (const filePath of filePaths) {\n const normalized = toPosixPath(filePath)\n if (!normalized.startsWith(rootPrefix)) continue\n\n const parts = normalized.slice(rootPrefix.length).split('/')\n let current = root\n const lastIndex = parts.length - 1\n for (const [i, part] of parts.entries()) {\n if (!part) continue\n\n const isLast = i === lastIndex\n const siblings = childIndex.get(current)!\n let child = siblings.get(part)\n if (!child) {\n child = { path: `${current.path}/${part}`, children: [], isFile: isLast }\n current.children.push(child)\n siblings.set(part, child)\n if (!isLast) childIndex.set(child, new Map())\n }\n current = child\n }\n }\n\n sortTree(root)\n\n return root\n}\n\nfunction sortTree(node: BuildTree): void {\n if (node.children.length === 0) return\n node.children.sort(compareByPath)\n\n for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n\nfunction toRelativeModulePath({ fromDir, filePath }: { fromDir: string; filePath: string }): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\ntype MakeBarrelParams = {\n dirPath: string\n exports: Array<ast.ExportNode>\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n reportedCollisions: Set<string>\n}\n\nfunction makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }: MakeBarrelParams): ast.FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ast.ExportNode> = []\n\n for (const item of exports) {\n const itemPath = posix.join(dirPath, item.path)\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\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\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n}\n\ntype LeafStrategy = (params: { dirPath: string; leafPath: string; sourceFile: ast.FileNode | undefined }) => Array<ast.ExportNode>\n\nfunction indexableNames({ sources, isTypeOnly }: { 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\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const sources = sourceFile?.sources ?? []\n if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return []\n return [ast.factory.createExport({ path: toRelativeModulePath({ fromDir: dirPath, filePath: leafPath }) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath({ fromDir: dirPath, filePath: leafPath })\n\n if (!sourceFile) return [ast.factory.createExport({ path: modulePath })]\n\n const valueNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: false })\n const typeNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: true })\n\n if (valueNames.length === 0 && typeNames.length === 0) {\n if (sourceFile.sources.length > 0) return []\n return [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ast.ExportNode> = []\n if (valueNames.length > 0) {\n exports.push(ast.factory.createExport({ name: valueNames, path: modulePath }))\n }\n if (typeNames.length > 0) {\n exports.push(ast.factory.createExport({ name: typeNames, path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\ntype WalkParams = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\n}\n\n/**\n * Post-order walk that yields a barrel per visited directory.\n * Returns the list of leaf file paths collected in this subtree (used by the parent call).\n */\nfunction* walkAllOrNamed(\n node: BuildTree,\n params: WalkParams,\n { isRoot, recursive }: { isRoot: boolean; recursive: boolean },\n): Generator<ast.FileNode, Array<string>> {\n const subtreeLeaves: Array<string> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)\n continue\n }\n\n const childLeaves = yield* walkAllOrNamed(child, params, { isRoot: false, recursive })\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\n }\n\n return subtreeLeaves\n}\n\n/**\n * Recursive walk that yields 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. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: WalkParams): Generator<ast.FileNode, boolean> {\n const exports: Array<ast.ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile: params.sourceFiles.get(child.path) }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath({ fromDir: node.path, filePath: `${child.path}${BARREL_SUFFIX}` }) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\n return true\n }\n\n return false\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: BuildTree\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 outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, ast.FileNode>()\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n }\n\n return { tree: buildTree(outputPath, [...sourceFiles.keys()]), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n nested?: boolean\n /**\n * Also generate a barrel for each sub-directory when nested is false.\n * No effect when nested is true (always generates hierarchical structure).\n */\n recursive?: boolean\n /**\n * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields 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 * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<ast.FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions }, { isRoot: true, recursive })\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport { definePlugin, Diagnostics } from '@kubb/kit'\nimport type { ast, Config, NormalizedPlugin, Plugin } from '@kubb/kit'\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\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 = definePlugin(() => {\n const excludedTargets: Array<string> = []\n const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\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 // 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 excludedTargets.push(target)\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 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) => !excludedTargets.some((target) => isPathInside(file.path, target)))\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-barrel",
3
- "version": "5.5.6",
3
+ "version": "5.6.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",
@@ -58,25 +58,18 @@
58
58
  "registry": "https://registry.npmjs.org/"
59
59
  },
60
60
  "dependencies": {
61
- "@kubb/ast": "5.5.6",
62
- "@kubb/core": "5.5.6"
61
+ "@kubb/kit": "5.6.1"
63
62
  },
64
63
  "devDependencies": {
65
- "@internals/utils": "0.0.1"
66
- },
67
- "peerDependencies": {
68
- "@kubb/core": "5.5.6"
64
+ "@internals/utils": "0.0.1",
65
+ "@kubb/core": "5.6.1"
69
66
  },
70
67
  "engines": {
71
68
  "node": ">=22"
72
69
  },
73
70
  "scripts": {
74
71
  "build": "tsdown",
75
- "clean": "node -e \"require('node:fs').rmSync('./dist', {recursive:true,force:true})\"",
76
- "lint": "oxlint .",
77
- "lint:fix": "oxlint --fix .",
78
72
  "start": "tsdown --watch",
79
- "test": "vitest --passWithNoTests",
80
- "typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false"
73
+ "typecheck": "tsc"
81
74
  }
82
75
  }