@kubb/plugin-barrel 5.0.0-beta.63 → 5.0.0-beta.65

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
@@ -373,8 +373,8 @@ const pluginBarrel = (0, _kubb_core.definePlugin)(() => {
373
373
  if (plugin.options.output.mode === "file") return;
374
374
  const barrelType = barrelConfig.type;
375
375
  const nested = barrelConfig.nested ?? false;
376
- const base = (0, node_path.resolve)(config.root, config.output.path);
377
- const target = (0, node_path.resolve)(base, plugin.options.output.path);
376
+ const base = node_path.default.resolve(config.root, config.output.path);
377
+ const target = node_path.default.resolve(base, plugin.options.output.path);
378
378
  const relative = node_path.default.relative(base, target);
379
379
  if (relative.startsWith("..") || node_path.default.isAbsolute(relative)) throw new Error("Invalid output path");
380
380
  for (const file of getBarrelFiles({
@@ -396,7 +396,7 @@ const pluginBarrel = (0, _kubb_core.definePlugin)(() => {
396
396
  if (barrelConfig === false) return;
397
397
  const barrelType = barrelConfig.type;
398
398
  for (const file of getBarrelFiles({
399
- outputPath: (0, node_path.resolve)(config.root, config.output.path),
399
+ outputPath: node_path.default.resolve(config.root, config.output.path),
400
400
  files: filteredFiles,
401
401
  barrelType
402
402
  })) upsertFile(file);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["factory","path"],"sources":["../../../internals/utils/src/fs.ts","../../../internals/utils/src/buildTree.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join, posix, resolve } from 'node:path'\nimport { camelCase } from './casing.ts'\nimport { runtime } from './runtime.ts'\n\n/**\n * Walks up the directory tree from `cwd` (defaults to `process.cwd()`) and\n * returns the absolute path of the nearest `package.json`, or `null` when none\n * is found before reaching the filesystem root.\n *\n * @example\n * ```ts\n * const pkgPath = findPackageJSON('/home/user/project/src') // '/home/user/project/package.json'\n * ```\n */\nexport function findPackageJSON(cwd?: string): string | null {\n let dir = cwd ? resolve(cwd) : process.cwd()\n while (true) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) return pkgPath\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/**\n * Converts all backslashes to forward slashes.\n * Extended-length Windows paths (`\\\\?\\...`) are left unchanged.\n */\nfunction toSlash(p: string): string {\n if (p.startsWith('\\\\\\\\?\\\\')) return p\n\n return p.replaceAll('\\\\', '/')\n}\n\n/**\n * Returns the relative path from `rootDir` to `filePath`, always using forward slashes\n * and prefixed with `./` when not already traversing upward.\n *\n * @example\n * ```ts\n * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'\n * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'\n * ```\n */\nexport function getRelativePath(rootDir?: string | null, filePath?: string | null): string {\n if (!rootDir || !filePath) {\n throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ''} ${filePath || ''}`)\n }\n\n const relativePath = posix.relative(toSlash(rootDir), toSlash(filePath))\n\n return relativePath.startsWith('../') ? relativePath : `./${relativePath}`\n}\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 * 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 * Writes `data` to `path`, trimming leading/trailing whitespace before saving.\n * Skips the write when the trimmed content is empty or identical to what is already on disk.\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 trimmed content\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 resolved = resolve(path)\n\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n const oldContent = (await file.exists()) ? await file.text() : null\n if (oldContent === trimmed) return null\n await Bun.write(resolved, trimmed)\n return trimmed\n }\n\n try {\n const oldContent = await readFile(resolved, { encoding: 'utf-8' })\n if (oldContent === trimmed) return null\n } catch {\n /* file doesn't exist yet */\n }\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, trimmed, { encoding: 'utf-8' })\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== trimmed) {\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 trimmed\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 * Builds a nested file path from a dotted name. Splits on dots that precede a letter\n * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases\n * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.\n *\n * Empty segments are dropped before joining. They arise when the name starts with a dot\n * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to\n * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an\n * absolute path, letting generated files escape the configured output directory.\n *\n * @example Nested path from a dotted name\n * `toFilePath('pet.petId') // 'pet/petId'`\n *\n * @example PascalCase the final segment\n * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`\n *\n * @example Suffix applied to the final segment only\n * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`\n */\nexport function toFilePath(name: string, caseLast: (part: string) => string = camelCase): string {\n const parts = name.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => (i === parts.length - 1 ? caseLast(part) : camelCase(part)))\n .filter(Boolean)\n .join('/')\n}\n","import { toPosixPath } from './fs.ts'\n\n/**\n * A node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\nexport type BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // 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 for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n","import { extname, resolve } from 'node:path'\nimport * as factory from '@kubb/ast/factory'\nimport type { ExportNode, FileNode, SourceNode } from '@kubb/ast'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { type BuildTree, buildTree, toPosixPath } from '@internals/utils'\nimport type { BarrelType } from './types.ts'\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst BARREL_SUFFIX = `/index.ts`\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {\n return factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports,\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 [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 [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 [factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(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}\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)\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.\n */\nfunction* walkNested(node: BuildTree): Generator<FileNode> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, child.path) }))\n continue\n }\n\n yield* walkNested(child)\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports)\n }\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Absolute directory the barrel(s) should be rooted at.\n * Only files living under this path are considered.\n */\n outputPath: string\n /**\n * Pool of generated files to scan for indexable sources.\n */\n files: ReadonlyArray<FileNode>\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n 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\n/**\n * Yields barrel `FileNode`s for the directory rooted at `outputPath`.\n *\n * @example\n * ```ts\n * for (const file of getBarrelFiles({ outputPath, files, barrelType })) {\n * upsertFile(file)\n * }\n * // or collect into an array\n * const barrels = [...getBarrelFiles({ outputPath, files, barrelType })]\n * ```\n */\nexport function* getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Generator<FileNode> {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n if (paths.length === 0) return\n\n const tree = buildTree(outputPath, paths)\n\n if (nested) {\n yield* walkNested(tree)\n return\n }\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n yield* walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, 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 return prefixes.values().some((prefix) => (prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix))\n}\n","import path from 'node:path'\nimport { resolve } from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, PluginBarrelConfig } from './types.ts'\nimport { 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.resolveBanner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.resolveFooter(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 `false` to disable barrel generation for this plugin entirely. Doing so also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default { type: 'named' }\n */\n barrel?: PluginBarrelConfig | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Barrel configuration for the root barrel file at `config.output.path/index.ts`.\n * Set to `false` to disable root barrel generation. Individual plugins can override\n * this via their own `output.barrel`.\n *\n * @default { type: 'named' }\n */\n barrel?: BarrelConfig | false\n }\n }\n }\n}\n\n/**\n * 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 event.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\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`.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `{ type: 'named' }`). Set `barrel: false` on a plugin to skip\n * its barrel and also exclude its files 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: { path: './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\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config, files, upsertFile }) {\n // Skip reactions to the barrel plugin's own lifecycle event\n if (plugin.name === pluginBarrelName) return\n\n const pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\n const defaultBarrel = { type: 'named' } as const\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 defaultBarrel\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 barrelType = barrelConfig.type\n const nested = barrelConfig.nested ?? false\n\n const base = resolve(config.root, config.output.path)\n const target = resolve(base, plugin.options.output.path)\n const relative = path.relative(base, target)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('Invalid output path')\n }\n for (const file of getBarrelFiles({ outputPath: target, files, barrelType, nested, recursive: true })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const barrelConfig = config.output.barrel ?? { type: 'named' }\n\n const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n if (barrelConfig === false) return\n\n const barrelType = barrelConfig.type\n\n for (const file of getBarrelFiles({ outputPath: resolve(config.root, config.output.path), files: filteredFiles, barrelType })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["factory","path"],"sources":["../../../internals/utils/src/fs.ts","../../../internals/utils/src/buildTree.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join, posix, resolve } from 'node:path'\nimport { camelCase } from './casing.ts'\nimport { runtime } from './runtime.ts'\n\n/**\n * Walks up the directory tree from `cwd` (defaults to `process.cwd()`) and\n * returns the absolute path of the nearest `package.json`, or `null` when none\n * is found before reaching the filesystem root.\n *\n * @example\n * ```ts\n * const pkgPath = findPackageJSON('/home/user/project/src') // '/home/user/project/package.json'\n * ```\n */\nexport function findPackageJSON(cwd?: string): string | null {\n let dir = cwd ? resolve(cwd) : process.cwd()\n while (true) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) return pkgPath\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/**\n * Converts all backslashes to forward slashes.\n * Extended-length Windows paths (`\\\\?\\...`) are left unchanged.\n */\nfunction toSlash(p: string): string {\n if (p.startsWith('\\\\\\\\?\\\\')) return p\n\n return p.replaceAll('\\\\', '/')\n}\n\n/**\n * Returns the relative path from `rootDir` to `filePath`, always using forward slashes\n * and prefixed with `./` when not already traversing upward.\n *\n * @example\n * ```ts\n * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'\n * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'\n * ```\n */\nexport function getRelativePath(rootDir?: string | null, filePath?: string | null): string {\n if (!rootDir || !filePath) {\n throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ''} ${filePath || ''}`)\n }\n\n const relativePath = posix.relative(toSlash(rootDir), toSlash(filePath))\n\n return relativePath.startsWith('../') ? relativePath : `./${relativePath}`\n}\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 * 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 * Writes `data` to `path`, trimming leading/trailing whitespace before saving.\n * Skips the write when the trimmed content is empty or identical to what is already on disk.\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 trimmed content\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 resolved = resolve(path)\n\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n const oldContent = (await file.exists()) ? await file.text() : null\n if (oldContent === trimmed) return null\n await Bun.write(resolved, trimmed)\n return trimmed\n }\n\n try {\n const oldContent = await readFile(resolved, { encoding: 'utf-8' })\n if (oldContent === trimmed) return null\n } catch {\n /* file doesn't exist yet */\n }\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, trimmed, { encoding: 'utf-8' })\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== trimmed) {\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 trimmed\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 * Builds a nested file path from a dotted name. Splits on dots that precede a letter\n * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases\n * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.\n *\n * Empty segments are dropped before joining. They arise when the name starts with a dot\n * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to\n * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an\n * absolute path, letting generated files escape the configured output directory.\n *\n * @example Nested path from a dotted name\n * `toFilePath('pet.petId') // 'pet/petId'`\n *\n * @example PascalCase the final segment\n * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`\n *\n * @example Suffix applied to the final segment only\n * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`\n */\nexport function toFilePath(name: string, caseLast: (part: string) => string = camelCase): string {\n const parts = name.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => (i === parts.length - 1 ? caseLast(part) : camelCase(part)))\n .filter(Boolean)\n .join('/')\n}\n","import { toPosixPath } from './fs.ts'\n\n/**\n * A node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\nexport type BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // 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 for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n","import { extname, resolve } from 'node:path'\nimport * as factory from '@kubb/ast/factory'\nimport type { ExportNode, FileNode, SourceNode } from '@kubb/ast'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { type BuildTree, buildTree, toPosixPath } from '@internals/utils'\nimport type { BarrelType } from './types.ts'\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst BARREL_SUFFIX = `/index.ts`\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {\n return factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports,\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 [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 [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 [factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(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}\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)\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.\n */\nfunction* walkNested(node: BuildTree): Generator<FileNode> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, child.path) }))\n continue\n }\n\n yield* walkNested(child)\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports)\n }\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Absolute directory the barrel(s) should be rooted at.\n * Only files living under this path are considered.\n */\n outputPath: string\n /**\n * Pool of generated files to scan for indexable sources.\n */\n files: ReadonlyArray<FileNode>\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n 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\n/**\n * Yields barrel `FileNode`s for the directory rooted at `outputPath`.\n *\n * @example\n * ```ts\n * for (const file of getBarrelFiles({ outputPath, files, barrelType })) {\n * upsertFile(file)\n * }\n * // or collect into an array\n * const barrels = [...getBarrelFiles({ outputPath, files, barrelType })]\n * ```\n */\nexport function* getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Generator<FileNode> {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n if (paths.length === 0) return\n\n const tree = buildTree(outputPath, paths)\n\n if (nested) {\n yield* walkNested(tree)\n return\n }\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n yield* walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, 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 return prefixes.values().some((prefix) => (prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix))\n}\n","import path from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, PluginBarrelConfig } from './types.ts'\nimport { 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.resolveBanner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.resolveFooter(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 `false` to disable barrel generation for this plugin entirely. Doing so also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default { type: 'named' }\n */\n barrel?: PluginBarrelConfig | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Barrel configuration for the root barrel file at `config.output.path/index.ts`.\n * Set to `false` to disable root barrel generation. Individual plugins can override\n * this via their own `output.barrel`.\n *\n * @default { type: 'named' }\n */\n barrel?: BarrelConfig | false\n }\n }\n }\n}\n\n/**\n * 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 event.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\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`.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `{ type: 'named' }`). Set `barrel: false` on a plugin to skip\n * its barrel and also exclude its files 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: { path: './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\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config, files, upsertFile }) {\n // Skip reactions to the barrel plugin's own lifecycle event\n if (plugin.name === pluginBarrelName) return\n\n const pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\n const defaultBarrel = { type: 'named' } as const\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 defaultBarrel\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 barrelType = barrelConfig.type\n const nested = barrelConfig.nested ?? false\n\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n const relative = path.relative(base, target)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('Invalid output path')\n }\n for (const file of getBarrelFiles({ outputPath: target, files, barrelType, nested, recursive: true })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const barrelConfig = config.output.barrel ?? { type: 'named' }\n\n const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n if (barrelConfig === false) return\n\n const barrelType = barrelConfig.type\n\n for (const file of getBarrelFiles({ outputPath: path.resolve(config.root, config.output.path), files: filteredFiles, barrelType })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as __name } from "./chunk-C0LytTxp.js";
1
+ import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
2
  import { Plugin } from "@kubb/core";
3
3
 
4
4
  //#region src/types.d.ts
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import "./chunk-C0LytTxp.js";
1
+ import "./rolldown-runtime-C0LytTxp.js";
2
2
  import path, { extname, resolve } from "node:path";
3
3
  import { definePlugin } from "@kubb/core";
4
4
  import * as factory from "@kubb/ast/factory";
@@ -349,8 +349,8 @@ const pluginBarrel = definePlugin(() => {
349
349
  if (plugin.options.output.mode === "file") return;
350
350
  const barrelType = barrelConfig.type;
351
351
  const nested = barrelConfig.nested ?? false;
352
- const base = resolve(config.root, config.output.path);
353
- const target = resolve(base, plugin.options.output.path);
352
+ const base = path.resolve(config.root, config.output.path);
353
+ const target = path.resolve(base, plugin.options.output.path);
354
354
  const relative = path.relative(base, target);
355
355
  if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error("Invalid output path");
356
356
  for (const file of getBarrelFiles({
@@ -372,7 +372,7 @@ const pluginBarrel = definePlugin(() => {
372
372
  if (barrelConfig === false) return;
373
373
  const barrelType = barrelConfig.type;
374
374
  for (const file of getBarrelFiles({
375
- outputPath: resolve(config.root, config.output.path),
375
+ outputPath: path.resolve(config.root, config.output.path),
376
376
  files: filteredFiles,
377
377
  barrelType
378
378
  })) upsertFile(file);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/fs.ts","../../../internals/utils/src/buildTree.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join, posix, resolve } from 'node:path'\nimport { camelCase } from './casing.ts'\nimport { runtime } from './runtime.ts'\n\n/**\n * Walks up the directory tree from `cwd` (defaults to `process.cwd()`) and\n * returns the absolute path of the nearest `package.json`, or `null` when none\n * is found before reaching the filesystem root.\n *\n * @example\n * ```ts\n * const pkgPath = findPackageJSON('/home/user/project/src') // '/home/user/project/package.json'\n * ```\n */\nexport function findPackageJSON(cwd?: string): string | null {\n let dir = cwd ? resolve(cwd) : process.cwd()\n while (true) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) return pkgPath\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/**\n * Converts all backslashes to forward slashes.\n * Extended-length Windows paths (`\\\\?\\...`) are left unchanged.\n */\nfunction toSlash(p: string): string {\n if (p.startsWith('\\\\\\\\?\\\\')) return p\n\n return p.replaceAll('\\\\', '/')\n}\n\n/**\n * Returns the relative path from `rootDir` to `filePath`, always using forward slashes\n * and prefixed with `./` when not already traversing upward.\n *\n * @example\n * ```ts\n * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'\n * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'\n * ```\n */\nexport function getRelativePath(rootDir?: string | null, filePath?: string | null): string {\n if (!rootDir || !filePath) {\n throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ''} ${filePath || ''}`)\n }\n\n const relativePath = posix.relative(toSlash(rootDir), toSlash(filePath))\n\n return relativePath.startsWith('../') ? relativePath : `./${relativePath}`\n}\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 * 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 * Writes `data` to `path`, trimming leading/trailing whitespace before saving.\n * Skips the write when the trimmed content is empty or identical to what is already on disk.\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 trimmed content\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 resolved = resolve(path)\n\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n const oldContent = (await file.exists()) ? await file.text() : null\n if (oldContent === trimmed) return null\n await Bun.write(resolved, trimmed)\n return trimmed\n }\n\n try {\n const oldContent = await readFile(resolved, { encoding: 'utf-8' })\n if (oldContent === trimmed) return null\n } catch {\n /* file doesn't exist yet */\n }\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, trimmed, { encoding: 'utf-8' })\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== trimmed) {\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 trimmed\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 * Builds a nested file path from a dotted name. Splits on dots that precede a letter\n * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases\n * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.\n *\n * Empty segments are dropped before joining. They arise when the name starts with a dot\n * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to\n * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an\n * absolute path, letting generated files escape the configured output directory.\n *\n * @example Nested path from a dotted name\n * `toFilePath('pet.petId') // 'pet/petId'`\n *\n * @example PascalCase the final segment\n * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`\n *\n * @example Suffix applied to the final segment only\n * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`\n */\nexport function toFilePath(name: string, caseLast: (part: string) => string = camelCase): string {\n const parts = name.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => (i === parts.length - 1 ? caseLast(part) : camelCase(part)))\n .filter(Boolean)\n .join('/')\n}\n","import { toPosixPath } from './fs.ts'\n\n/**\n * A node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\nexport type BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // 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 for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n","import { extname, resolve } from 'node:path'\nimport * as factory from '@kubb/ast/factory'\nimport type { ExportNode, FileNode, SourceNode } from '@kubb/ast'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { type BuildTree, buildTree, toPosixPath } from '@internals/utils'\nimport type { BarrelType } from './types.ts'\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst BARREL_SUFFIX = `/index.ts`\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {\n return factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports,\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 [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 [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 [factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(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}\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)\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.\n */\nfunction* walkNested(node: BuildTree): Generator<FileNode> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, child.path) }))\n continue\n }\n\n yield* walkNested(child)\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports)\n }\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Absolute directory the barrel(s) should be rooted at.\n * Only files living under this path are considered.\n */\n outputPath: string\n /**\n * Pool of generated files to scan for indexable sources.\n */\n files: ReadonlyArray<FileNode>\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n 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\n/**\n * Yields barrel `FileNode`s for the directory rooted at `outputPath`.\n *\n * @example\n * ```ts\n * for (const file of getBarrelFiles({ outputPath, files, barrelType })) {\n * upsertFile(file)\n * }\n * // or collect into an array\n * const barrels = [...getBarrelFiles({ outputPath, files, barrelType })]\n * ```\n */\nexport function* getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Generator<FileNode> {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n if (paths.length === 0) return\n\n const tree = buildTree(outputPath, paths)\n\n if (nested) {\n yield* walkNested(tree)\n return\n }\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n yield* walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, 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 return prefixes.values().some((prefix) => (prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix))\n}\n","import path from 'node:path'\nimport { resolve } from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, PluginBarrelConfig } from './types.ts'\nimport { 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.resolveBanner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.resolveFooter(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 `false` to disable barrel generation for this plugin entirely. Doing so also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default { type: 'named' }\n */\n barrel?: PluginBarrelConfig | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Barrel configuration for the root barrel file at `config.output.path/index.ts`.\n * Set to `false` to disable root barrel generation. Individual plugins can override\n * this via their own `output.barrel`.\n *\n * @default { type: 'named' }\n */\n barrel?: BarrelConfig | false\n }\n }\n }\n}\n\n/**\n * 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 event.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\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`.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `{ type: 'named' }`). Set `barrel: false` on a plugin to skip\n * its barrel and also exclude its files 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: { path: './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\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config, files, upsertFile }) {\n // Skip reactions to the barrel plugin's own lifecycle event\n if (plugin.name === pluginBarrelName) return\n\n const pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\n const defaultBarrel = { type: 'named' } as const\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 defaultBarrel\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 barrelType = barrelConfig.type\n const nested = barrelConfig.nested ?? false\n\n const base = resolve(config.root, config.output.path)\n const target = resolve(base, plugin.options.output.path)\n const relative = path.relative(base, target)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('Invalid output path')\n }\n for (const file of getBarrelFiles({ outputPath: target, files, barrelType, nested, recursive: true })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const barrelConfig = config.output.barrel ?? { type: 'named' }\n\n const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n if (barrelConfig === false) return\n\n const barrelType = barrelConfig.type\n\n for (const file of getBarrelFiles({ outputPath: resolve(config.root, config.output.path), files: filteredFiles, barrelType })) {\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","../../../internals/utils/src/buildTree.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join, posix, resolve } from 'node:path'\nimport { camelCase } from './casing.ts'\nimport { runtime } from './runtime.ts'\n\n/**\n * Walks up the directory tree from `cwd` (defaults to `process.cwd()`) and\n * returns the absolute path of the nearest `package.json`, or `null` when none\n * is found before reaching the filesystem root.\n *\n * @example\n * ```ts\n * const pkgPath = findPackageJSON('/home/user/project/src') // '/home/user/project/package.json'\n * ```\n */\nexport function findPackageJSON(cwd?: string): string | null {\n let dir = cwd ? resolve(cwd) : process.cwd()\n while (true) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) return pkgPath\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/**\n * Converts all backslashes to forward slashes.\n * Extended-length Windows paths (`\\\\?\\...`) are left unchanged.\n */\nfunction toSlash(p: string): string {\n if (p.startsWith('\\\\\\\\?\\\\')) return p\n\n return p.replaceAll('\\\\', '/')\n}\n\n/**\n * Returns the relative path from `rootDir` to `filePath`, always using forward slashes\n * and prefixed with `./` when not already traversing upward.\n *\n * @example\n * ```ts\n * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'\n * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'\n * ```\n */\nexport function getRelativePath(rootDir?: string | null, filePath?: string | null): string {\n if (!rootDir || !filePath) {\n throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ''} ${filePath || ''}`)\n }\n\n const relativePath = posix.relative(toSlash(rootDir), toSlash(filePath))\n\n return relativePath.startsWith('../') ? relativePath : `./${relativePath}`\n}\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 * 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 * Writes `data` to `path`, trimming leading/trailing whitespace before saving.\n * Skips the write when the trimmed content is empty or identical to what is already on disk.\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 trimmed content\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 resolved = resolve(path)\n\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n const oldContent = (await file.exists()) ? await file.text() : null\n if (oldContent === trimmed) return null\n await Bun.write(resolved, trimmed)\n return trimmed\n }\n\n try {\n const oldContent = await readFile(resolved, { encoding: 'utf-8' })\n if (oldContent === trimmed) return null\n } catch {\n /* file doesn't exist yet */\n }\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, trimmed, { encoding: 'utf-8' })\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== trimmed) {\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 trimmed\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 * Builds a nested file path from a dotted name. Splits on dots that precede a letter\n * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases\n * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.\n *\n * Empty segments are dropped before joining. They arise when the name starts with a dot\n * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to\n * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an\n * absolute path, letting generated files escape the configured output directory.\n *\n * @example Nested path from a dotted name\n * `toFilePath('pet.petId') // 'pet/petId'`\n *\n * @example PascalCase the final segment\n * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`\n *\n * @example Suffix applied to the final segment only\n * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`\n */\nexport function toFilePath(name: string, caseLast: (part: string) => string = camelCase): string {\n const parts = name.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => (i === parts.length - 1 ? caseLast(part) : camelCase(part)))\n .filter(Boolean)\n .join('/')\n}\n","import { toPosixPath } from './fs.ts'\n\n/**\n * A node in the directory tree used to compute barrel file exports.\n * Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).\n */\nexport type BuildTree = {\n /**\n * Absolute filesystem path of this directory or file. Always normalized to POSIX (`/`) separators.\n */\n path: string\n /**\n * Sub-directories and files contained within this directory.\n * Always empty for file nodes.\n */\n children: Array<BuildTree>\n /**\n * `true` when this node represents a file (leaf), `false` for directory nodes.\n */\n isFile: boolean\n}\n\n/**\n * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.\n * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically\n * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.\n *\n * Both POSIX (`/`) and Windows (`\\`) separators are accepted in input paths; emitted node\n * paths are always POSIX-normalized so downstream prefix/lookup operations behave the same\n * across platforms.\n *\n * @example\n * ```ts\n * buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {\n const normalizedRoot = toPosixPath(rootPath)\n const root: BuildTree = { path: normalizedRoot, children: [], isFile: false }\n // Per-directory child lookup avoids the O(N) `Array.find` scan during insertion.\n // 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 for (const child of node.children) {\n if (!child.isFile) sortTree(child)\n }\n}\n\nfunction compareByPath(a: BuildTree, b: BuildTree): number {\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0\n}\n","import { extname, resolve } from 'node:path'\nimport * as factory from '@kubb/ast/factory'\nimport type { ExportNode, FileNode, SourceNode } from '@kubb/ast'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { type BuildTree, buildTree, toPosixPath } from '@internals/utils'\nimport type { BarrelType } from './types.ts'\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst BARREL_SUFFIX = `/index.ts`\n\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n return `./${filePath.slice(fromDir.length + 1)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\nfunction makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {\n return factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports,\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 [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 [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 [factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(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}\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)\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.\n */\nfunction* walkNested(node: BuildTree): Generator<FileNode> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, child.path) }))\n continue\n }\n\n yield* walkNested(child)\n exports.push(factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports)\n }\n}\n\ntype IndexedFiles = {\n sourceFiles: ReadonlyMap<string, FileNode>\n paths: ReadonlyArray<string>\n}\n\nfunction indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, FileNode>()\n const paths: Array<string> = []\n\n for (const file of files) {\n const normalized = toPosixPath(file.path)\n if (!normalized.startsWith(outputPrefix)) continue\n if (isBarrelPath(normalized)) continue\n if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue\n\n sourceFiles.set(normalized, file)\n paths.push(normalized)\n }\n\n return { sourceFiles, paths }\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Absolute directory the barrel(s) should be rooted at.\n * Only files living under this path are considered.\n */\n outputPath: string\n /**\n * Pool of generated files to scan for indexable sources.\n */\n files: ReadonlyArray<FileNode>\n /**\n * Export strategy used when emitting each barrel.\n * - `'all'` re-exports the whole module (`export * from './x'`)\n * - `'named'` re-exports only the indexable named symbols\n */\n barrelType: BarrelType\n /**\n * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).\n * When false, uses flat generation strategy with optional recursive subdirectory barrels.\n */\n 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\n/**\n * Yields barrel `FileNode`s for the directory rooted at `outputPath`.\n *\n * @example\n * ```ts\n * for (const file of getBarrelFiles({ outputPath, files, barrelType })) {\n * upsertFile(file)\n * }\n * // or collect into an array\n * const barrels = [...getBarrelFiles({ outputPath, files, barrelType })]\n * ```\n */\nexport function* getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Generator<FileNode> {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n if (paths.length === 0) return\n\n const tree = buildTree(outputPath, paths)\n\n if (nested) {\n yield* walkNested(tree)\n return\n }\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n yield* walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, 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 return prefixes.values().some((prefix) => (prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix))\n}\n","import path from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, PluginBarrelConfig } from './types.ts'\nimport { 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.resolveBanner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.resolveFooter(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 `false` to disable barrel generation for this plugin entirely. Doing so also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default { type: 'named' }\n */\n barrel?: PluginBarrelConfig | false\n }\n }\n interface ConfigOptionsRegistry {\n output: {\n /**\n * Barrel configuration for the root barrel file at `config.output.path/index.ts`.\n * Set to `false` to disable root barrel generation. Individual plugins can override\n * this via their own `output.barrel`.\n *\n * @default { type: 'named' }\n */\n barrel?: BarrelConfig | false\n }\n }\n }\n}\n\n/**\n * 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 event.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\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`.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `{ type: 'named' }`). Set `barrel: false` on a plugin to skip\n * its barrel and also exclude its files 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: { path: './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\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config, files, upsertFile }) {\n // Skip reactions to the barrel plugin's own lifecycle event\n if (plugin.name === pluginBarrelName) return\n\n const pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\n const defaultBarrel = { type: 'named' } as const\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 defaultBarrel\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 barrelType = barrelConfig.type\n const nested = barrelConfig.nested ?? false\n\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n const relative = path.relative(base, target)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('Invalid output path')\n }\n for (const file of getBarrelFiles({ outputPath: target, files, barrelType, nested, recursive: true })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const barrelConfig = config.output.barrel ?? { type: 'named' }\n\n const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n if (barrelConfig === false) return\n\n const barrelType = barrelConfig.type\n\n for (const file of getBarrelFiles({ outputPath: path.resolve(config.root, config.output.path), files: filteredFiles, barrelType })) {\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.0.0-beta.63",
3
+ "version": "5.0.0-beta.65",
4
4
  "description": "Barrel-file plugin for Kubb. Automatically generates index.ts re-export files per plugin output directory and an optional root barrel after all plugins complete.",
5
5
  "keywords": [
6
6
  "barrel",
@@ -17,7 +17,6 @@
17
17
  "directory": "packages/plugin-barrel"
18
18
  },
19
19
  "files": [
20
- "src",
21
20
  "dist",
22
21
  "*.d.ts",
23
22
  "*.d.cts",
@@ -42,14 +41,14 @@
42
41
  "registry": "https://registry.npmjs.org/"
43
42
  },
44
43
  "dependencies": {
45
- "@kubb/ast": "5.0.0-beta.63",
46
- "@kubb/core": "5.0.0-beta.63"
44
+ "@kubb/ast": "5.0.0-beta.65",
45
+ "@kubb/core": "5.0.0-beta.65"
47
46
  },
48
47
  "devDependencies": {
49
48
  "@internals/utils": "0.0.0"
50
49
  },
51
50
  "peerDependencies": {
52
- "@kubb/core": "5.0.0-beta.63"
51
+ "@kubb/core": "5.0.0-beta.65"
53
52
  },
54
53
  "engines": {
55
54
  "node": ">=22"
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { pluginBarrel, pluginBarrelName } from './plugin.ts'
2
- export type { BarrelType, BarrelConfig, PluginBarrelConfig } from './types.ts'
package/src/plugin.ts DELETED
@@ -1,162 +0,0 @@
1
- import path from 'node:path'
2
- import { resolve } from 'node:path'
3
- import type { FileNode } from '@kubb/ast'
4
- import { definePlugin } from '@kubb/core'
5
- import type { Config, NormalizedPlugin, Plugin } from '@kubb/core'
6
- import type { BarrelConfig, PluginBarrelConfig } from './types.ts'
7
- import { getBarrelFiles, getPluginOutputPrefix, isExcludedPath } from './utils.ts'
8
-
9
- /**
10
- * Applies a plugin's configured `output.banner`/`footer` to a barrel file, flagged as `isBarrel`.
11
- *
12
- * Resolves through the plugin's own resolver, and only when the plugin explicitly sets a
13
- * banner/footer, so barrels stay banner-free by default and never inherit the implicit
14
- * "Generated by Kubb" notice.
15
- */
16
- function withBarrelBannerFooter({ file, plugin, config }: { file: FileNode; plugin: NormalizedPlugin; config: Config }): FileNode {
17
- const output = plugin.options?.output
18
- const resolver = plugin.resolver
19
- if (!resolver) return file
20
-
21
- const hasBanner = output?.banner !== undefined
22
- const hasFooter = output?.footer !== undefined
23
- if (!hasBanner && !hasFooter) return file
24
-
25
- const context = { output, config, file: { path: file.path, baseName: file.baseName, isBarrel: true } }
26
- return {
27
- ...file,
28
- banner: hasBanner ? resolver.resolveBanner(undefined, context) : file.banner,
29
- footer: hasFooter ? resolver.resolveFooter(undefined, context) : file.footer,
30
- }
31
- }
32
-
33
- declare global {
34
- namespace Kubb {
35
- interface PluginOptionsRegistry {
36
- output: {
37
- /**
38
- * Barrel configuration for this plugin's output.
39
- * Set to `false` to disable barrel generation for this plugin entirely. Doing so also
40
- * excludes the plugin's files from the root barrel.
41
- *
42
- * Falls back to `config.output.barrel` when omitted.
43
- *
44
- * @default { type: 'named' }
45
- */
46
- barrel?: PluginBarrelConfig | false
47
- }
48
- }
49
- interface ConfigOptionsRegistry {
50
- output: {
51
- /**
52
- * Barrel configuration for the root barrel file at `config.output.path/index.ts`.
53
- * Set to `false` to disable root barrel generation. Individual plugins can override
54
- * this via their own `output.barrel`.
55
- *
56
- * @default { type: 'named' }
57
- */
58
- barrel?: BarrelConfig | false
59
- }
60
- }
61
- }
62
- }
63
-
64
- /**
65
- * Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups
66
- * and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle event.
67
- */
68
- export const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']
69
-
70
- /**
71
- * Generates an `index.ts` for every plugin output directory and one root
72
- * barrel at `config.output.path/index.ts` after the build completes. Ships
73
- * with Kubb and is registered by default in `defineConfig`.
74
- *
75
- * Each plugin inherits `output.barrel` from `config.output.barrel` (which
76
- * defaults to `{ type: 'named' }`). Set `barrel: false` on a plugin to skip
77
- * its barrel and also exclude its files from the root barrel.
78
- *
79
- * A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output
80
- * is a single file. The root barrel re-exports that file directly.
81
- *
82
- * @example
83
- * ```ts
84
- * import { defineConfig } from '@kubb/core'
85
- * import { pluginBarrel } from '@kubb/plugin-barrel'
86
- * import { pluginTs } from '@kubb/plugin-ts'
87
- * import { pluginZod } from '@kubb/plugin-zod'
88
- *
89
- * export default defineConfig({
90
- * input: { path: './petStore.yaml' },
91
- * output: { path: 'src/gen', barrel: { type: 'named' } },
92
- * plugins: [
93
- * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
94
- * pluginZod({ output: { path: 'schemas' } }),
95
- * pluginBarrel(),
96
- * ],
97
- * })
98
- * ```
99
- */
100
- export const pluginBarrel = definePlugin(() => {
101
- const excludedPrefixes = new Set<string>()
102
-
103
- return {
104
- name: pluginBarrelName,
105
- enforce: 'post' as const,
106
- hooks: {
107
- 'kubb:plugin:end'({ plugin, config, files, upsertFile }) {
108
- // Skip reactions to the barrel plugin's own lifecycle event
109
- if (plugin.name === pluginBarrelName) return
110
-
111
- const pluginBarrelOpt = plugin.options.output?.barrel
112
- const configBarrel = config.output.barrel
113
- const defaultBarrel = { type: 'named' } as const
114
-
115
- // Root config barrel doesn't have nested, so we add it
116
- const barrelConfig: PluginBarrelConfig | false = (() => {
117
- if (pluginBarrelOpt !== undefined) return pluginBarrelOpt
118
- if (configBarrel !== undefined) return configBarrel === false ? false : { ...configBarrel, nested: false }
119
- return defaultBarrel
120
- })()
121
-
122
- if (barrelConfig === false) {
123
- excludedPrefixes.add(getPluginOutputPrefix(plugin, config))
124
- return
125
- }
126
-
127
- // `mode: 'file'` writes a single file, so there is no directory to barrel. The root barrel
128
- // re-exports that file as a direct leaf of `config.output.path`.
129
- if (plugin.options.output.mode === 'file') {
130
- return
131
- }
132
-
133
- const barrelType = barrelConfig.type
134
- const nested = barrelConfig.nested ?? false
135
-
136
- const base = resolve(config.root, config.output.path)
137
- const target = resolve(base, plugin.options.output.path)
138
- const relative = path.relative(base, target)
139
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
140
- throw new Error('Invalid output path')
141
- }
142
- for (const file of getBarrelFiles({ outputPath: target, files, barrelType, nested, recursive: true })) {
143
- upsertFile(withBarrelBannerFooter({ file, plugin, config }))
144
- }
145
- },
146
- 'kubb:plugins:end'({ files, config, upsertFile }) {
147
- const barrelConfig = config.output.barrel ?? { type: 'named' }
148
-
149
- const filteredFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))
150
- excludedPrefixes.clear()
151
-
152
- if (barrelConfig === false) return
153
-
154
- const barrelType = barrelConfig.type
155
-
156
- for (const file of getBarrelFiles({ outputPath: resolve(config.root, config.output.path), files: filteredFiles, barrelType })) {
157
- upsertFile(file)
158
- }
159
- },
160
- },
161
- }
162
- })
package/src/types.ts DELETED
@@ -1,52 +0,0 @@
1
- /**
2
- * Barrel export strategy.
3
- *
4
- * - `'all'` generates `export * from '...'` for every file
5
- * - `'named'` generates `export { name1, name2 } from '...'` using each file's named exports
6
- */
7
- export type BarrelType = 'all' | 'named'
8
-
9
- /**
10
- * Barrel configuration at the root config level.
11
- *
12
- * @example
13
- * ```ts
14
- * barrel: { type: 'named' } // default
15
- * barrel: { type: 'all' }
16
- * barrel: false // disable barrel generation
17
- * ```
18
- */
19
- export type BarrelConfig = {
20
- /**
21
- * Export strategy for the root barrel file.
22
- * - `'all'` wildcard exports: `export * from './file'`
23
- * - `'named'` explicit exports: `export { x, y } from './file'`
24
- */
25
- type: BarrelType
26
- }
27
-
28
- /**
29
- * Barrel configuration at the plugin level.
30
- * Supports nested barrel generation in subdirectories.
31
- *
32
- * @example
33
- * ```ts
34
- * barrel: { type: 'named' } // single barrel with named exports
35
- * barrel: { type: 'all', nested: true } // hierarchical barrels with wildcard exports
36
- * barrel: { type: 'named', nested: true } // hierarchical barrels with named exports
37
- * barrel: false // disable barrel generation
38
- * ```
39
- */
40
- export type PluginBarrelConfig = {
41
- /**
42
- * Export strategy for the plugin's barrel files.
43
- * - `'all'` wildcard exports: `export * from './file'`
44
- * - `'named'` explicit exports: `export { x, y } from './file'`
45
- */
46
- type: BarrelType
47
- /**
48
- * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it.
49
- * Creates a hierarchical barrel structure instead of flat exports from the root.
50
- */
51
- nested?: boolean
52
- }
package/src/utils.ts DELETED
@@ -1,254 +0,0 @@
1
- import { extname, resolve } from 'node:path'
2
- import * as factory from '@kubb/ast/factory'
3
- import type { ExportNode, FileNode, SourceNode } from '@kubb/ast'
4
- import type { Config, NormalizedPlugin } from '@kubb/core'
5
- import { type BuildTree, buildTree, toPosixPath } from '@internals/utils'
6
- import type { BarrelType } from './types.ts'
7
-
8
- const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])
9
- const BARREL_SUFFIX = `/index.ts`
10
-
11
- function toRelativeModulePath(fromDir: string, filePath: string): string {
12
- return `./${filePath.slice(fromDir.length + 1)}`
13
- }
14
-
15
- function isBarrelPath(path: string): boolean {
16
- return path.endsWith(BARREL_SUFFIX)
17
- }
18
-
19
- function makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {
20
- return factory.createFile({
21
- baseName: 'index.ts',
22
- path: `${dirPath}${BARREL_SUFFIX}`,
23
- exports,
24
- sources: [],
25
- imports: [],
26
- // Default to no banner/footer. The barrel plugin resolves a configured plugin
27
- // banner/footer (with isBarrel: true) afterwards, so a `banner` function can
28
- // decide per file whether a barrel should carry a directive like "use server".
29
- banner: undefined,
30
- footer: undefined,
31
- })
32
- }
33
-
34
- type LeafContext = {
35
- dirPath: string
36
- leafPath: string
37
- sourceFile: FileNode | null
38
- }
39
-
40
- type LeafStrategy = (ctx: LeafContext) => Array<ExportNode>
41
-
42
- function hasOnlyNonIndexableSources(sources: ReadonlyArray<SourceNode>): boolean {
43
- if (sources.length === 0) return false
44
- for (const source of sources) {
45
- if (source.isIndexable) return false
46
- }
47
- return true
48
- }
49
-
50
- function partitionIndexableNames(sources: ReadonlyArray<SourceNode>): Map<boolean, Set<string>> {
51
- const byTypeOnly = new Map<boolean, Set<string>>([
52
- [false, new Set()],
53
- [true, new Set()],
54
- ])
55
- for (const source of sources) {
56
- if (!source.isIndexable || !source.name) continue
57
- byTypeOnly.get(Boolean(source.isTypeOnly))!.add(source.name)
58
- }
59
- return byTypeOnly
60
- }
61
-
62
- const allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {
63
- if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return []
64
- return [factory.createExport({ path: toRelativeModulePath(dirPath, leafPath) })]
65
- }
66
-
67
- const namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {
68
- const modulePath = toRelativeModulePath(dirPath, leafPath)
69
-
70
- if (!sourceFile) return [factory.createExport({ path: modulePath })]
71
-
72
- const namesByTypeOnly = partitionIndexableNames(sourceFile.sources)
73
- const valueNames = namesByTypeOnly.get(false)!
74
- const typeNames = namesByTypeOnly.get(true)!
75
-
76
- if (valueNames.size === 0 && typeNames.size === 0) {
77
- if (sourceFile.sources.length > 0) return []
78
- return [factory.createExport({ path: modulePath })]
79
- }
80
-
81
- const exports: Array<ExportNode> = []
82
- if (valueNames.size > 0) {
83
- exports.push(factory.createExport({ name: [...valueNames].sort(), path: modulePath }))
84
- }
85
- if (typeNames.size > 0) {
86
- exports.push(factory.createExport({ name: [...typeNames].sort(), path: modulePath, isTypeOnly: true }))
87
- }
88
- return exports
89
- }
90
-
91
- const LEAF_STRATEGIES: ReadonlyMap<BarrelType, LeafStrategy> = new Map([
92
- ['all', allStrategy],
93
- ['named', namedStrategy],
94
- ])
95
-
96
- type LeafWalkParams = {
97
- sourceFiles: ReadonlyMap<string, FileNode>
98
- strategy: LeafStrategy
99
- recursive: boolean
100
- }
101
-
102
- /**
103
- * Post-order walk that yields a barrel per visited directory.
104
- * Returns the list of leaf file paths collected in this subtree (used by the parent call).
105
- */
106
- function* walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean): Generator<FileNode, Array<string>> {
107
- const subtreeLeaves: Array<string> = []
108
-
109
- for (const child of node.children) {
110
- if (child.isFile) {
111
- if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)
112
- continue
113
- }
114
-
115
- const childLeaves = yield* walkAllOrNamed(child, params, false)
116
- for (const leaf of childLeaves) subtreeLeaves.push(leaf)
117
- }
118
-
119
- if (!isRoot && !params.recursive) return subtreeLeaves
120
-
121
- const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) ?? null }))
122
-
123
- if (exports.length > 0) {
124
- yield makeBarrel(node.path, exports)
125
- }
126
-
127
- return subtreeLeaves
128
- }
129
-
130
- /**
131
- * Recursive walk that yields one barrel per directory, re-exporting files and sub-barrels.
132
- * Used when nested: true.
133
- */
134
- function* walkNested(node: BuildTree): Generator<FileNode> {
135
- const exports: Array<ExportNode> = []
136
-
137
- for (const child of node.children) {
138
- if (child.isFile) {
139
- if (isBarrelPath(child.path)) continue
140
- exports.push(factory.createExport({ path: toRelativeModulePath(node.path, child.path) }))
141
- continue
142
- }
143
-
144
- yield* walkNested(child)
145
- exports.push(factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))
146
- }
147
-
148
- if (exports.length > 0) {
149
- yield makeBarrel(node.path, exports)
150
- }
151
- }
152
-
153
- type IndexedFiles = {
154
- sourceFiles: ReadonlyMap<string, FileNode>
155
- paths: ReadonlyArray<string>
156
- }
157
-
158
- function indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {
159
- const outputPrefix = `${toPosixPath(outputPath)}/`
160
- const sourceFiles = new Map<string, FileNode>()
161
- const paths: Array<string> = []
162
-
163
- for (const file of files) {
164
- const normalized = toPosixPath(file.path)
165
- if (!normalized.startsWith(outputPrefix)) continue
166
- if (isBarrelPath(normalized)) continue
167
- if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue
168
-
169
- sourceFiles.set(normalized, file)
170
- paths.push(normalized)
171
- }
172
-
173
- return { sourceFiles, paths }
174
- }
175
-
176
- type GetBarrelFilesParams = {
177
- /**
178
- * Absolute directory the barrel(s) should be rooted at.
179
- * Only files living under this path are considered.
180
- */
181
- outputPath: string
182
- /**
183
- * Pool of generated files to scan for indexable sources.
184
- */
185
- files: ReadonlyArray<FileNode>
186
- /**
187
- * Export strategy used when emitting each barrel.
188
- * - `'all'` re-exports the whole module (`export * from './x'`)
189
- * - `'named'` re-exports only the indexable named symbols
190
- */
191
- barrelType: BarrelType
192
- /**
193
- * Generate an `index.ts` in every sub-directory, each re-exporting only what's directly inside it (hierarchical).
194
- * When false, uses flat generation strategy with optional recursive subdirectory barrels.
195
- */
196
- nested?: boolean
197
- /**
198
- * Also generate a barrel for each sub-directory when nested is false.
199
- * No effect when nested is true (always generates hierarchical structure).
200
- */
201
- recursive?: boolean
202
- }
203
-
204
- /**
205
- * Yields barrel `FileNode`s for the directory rooted at `outputPath`.
206
- *
207
- * @example
208
- * ```ts
209
- * for (const file of getBarrelFiles({ outputPath, files, barrelType })) {
210
- * upsertFile(file)
211
- * }
212
- * // or collect into an array
213
- * const barrels = [...getBarrelFiles({ outputPath, files, barrelType })]
214
- * ```
215
- */
216
- export function* getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Generator<FileNode> {
217
- const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)
218
- if (paths.length === 0) return
219
-
220
- const tree = buildTree(outputPath, paths)
221
-
222
- if (nested) {
223
- yield* walkNested(tree)
224
- return
225
- }
226
-
227
- const strategy = LEAF_STRATEGIES.get(barrelType)
228
- if (!strategy) return
229
-
230
- yield* walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, true)
231
- }
232
-
233
- /**
234
- * Builds a POSIX-normalized prefix for a plugin's output. A directory output gets a trailing `/`,
235
- * while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.
236
- *
237
- * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
238
- */
239
- export function getPluginOutputPrefix(plugin: NormalizedPlugin, config: Config): string {
240
- const resolved = toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))
241
- return plugin.options.output.mode === 'file' ? resolved : `${resolved}/`
242
- }
243
-
244
- /**
245
- * Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a
246
- * trailing `/` matches a directory subtree, and a prefix without one matches that exact file
247
- * (used for `mode: 'file'` outputs).
248
- *
249
- * Both sides are POSIX-normalized so Windows backslash paths match correctly.
250
- */
251
- export function isExcludedPath(filePath: string, prefixes: ReadonlySet<string>): boolean {
252
- const normalized = toPosixPath(filePath)
253
- return prefixes.values().some((prefix) => (prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix))
254
- }