@kubb/middleware-barrel 5.0.0-beta.5 → 5.0.0-beta.50

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/extension.yaml CHANGED
@@ -2,7 +2,7 @@ $schema: https://kubb.dev/schemas/extension.json
2
2
  kind: middleware
3
3
  id: middleware-barrel
4
4
  name: Barrel
5
- description: Automatically generates barrel files (index.ts exports) for every plugin output directory. Ships with Kubb and is enabled by default.
5
+ description: Generates `index.ts` re-export files for every plugin output and one root barrel. Ships with Kubb and is enabled by default.
6
6
  category: output
7
7
  type: official
8
8
  npmPackage: '@kubb/middleware-barrel'
@@ -26,9 +26,9 @@ resources:
26
26
  changelog: https://github.com/kubb-labs/kubb/blob/main/packages/middleware-barrel/CHANGELOG.md
27
27
  featured: true
28
28
  intro: |-
29
- The barrel middleware automatically generates `index.ts` (and `index.js`) re-export files for every plugin output directory **and** a root barrel at `config.output.path/index.ts` after the build finishes.
29
+ `@kubb/middleware-barrel` generates an `index.ts` for every plugin output directory and one root barrel at `output.path/index.ts` after the build completes. The result is a single import surface for every consumer — `import { Pet, usePetByIdQuery, petMock } from './gen'`.
30
30
 
31
- It ships with Kubb and is registered as a default middleware in `defineConfig`, so you get barrel files out of the box with no extra configuration. When `middlewareBarrel` is part of `config.middleware`, `defineConfig` also applies a default `output.barrel` of `{ type: 'named' }`. Custom middleware lists that omit `middlewareBarrel` leave `barrel` untouched.
31
+ The middleware ships with Kubb and is registered by default in `defineConfig`, so barrels appear out of the box with no extra configuration. When `middlewareBarrel` is part of `config.middleware`, `defineConfig` also applies a default `output.barrel` of `{ type: 'named' }`. Custom middleware lists that omit `middlewareBarrel` leave `barrel` untouched.
32
32
 
33
33
  Plugins inherit `output.barrel` from `config.output.barrel` when their own value is omitted. Setting `barrel: false` on a plugin disables that plugin's barrel **and** excludes its files from the root barrel.
34
34
  options:
@@ -37,18 +37,20 @@ options:
37
37
  required: false
38
38
  default: "{ type: 'named' }"
39
39
  description: |-
40
- Re-export style for the generated barrel files. Configured via `output.barrel` in `defineConfig` (root level) or per plugin via the plugin's own `output.barrel`. This is not a middleware argument.
40
+ Re-export style used for every generated barrel file. Set via `output.barrel` in `defineConfig` (applies to all plugins and the root barrel) or per plugin via that plugin's own `output.barrel`. It is not an argument to the middleware itself.
41
41
 
42
42
  At the config level:
43
- - `{ type: 'all' }` — `export * from '...'` for every generated file
44
- - `{ type: 'named' }` — `export { name1, name2 } from '...'` using each file's named exports
45
- - `false` — disables barrel generation entirely
46
43
 
47
- At the plugin level, the `nested` flag is also available:
48
- - `{ type: 'named', nested: true }` — generates a barrel for every directory, re-exporting only direct children, creating a chain for imports from any depth
49
- - `false` — disables barrel generation for that plugin and excludes its files from the root barrel
44
+ - `{ type: 'all' }` `export * from '...'` for every generated file.
45
+ - `{ type: 'named' }` — `export { Foo, Bar } from '...'` using each file's named exports.
46
+ - `false` — disables barrel generation entirely.
50
47
 
51
- The `{ type: 'named' }` default is applied automatically by `defineConfig` only when `middlewareBarrel` is part of `config.middleware`.
48
+ At the plugin level you also get `nested`:
49
+
50
+ - `{ type: 'named', nested: true }` — generates a barrel for every subdirectory, re-exporting only direct children. Lets callers import from any depth.
51
+ - `false` — disables the plugin's barrel and removes its files from the root barrel.
52
+
53
+ The `{ type: 'named' }` default kicks in only when `middlewareBarrel` is part of `config.middleware`.
52
54
  codeBlock:
53
55
  lang: typescript
54
56
  title: kubb.config.ts
@@ -58,12 +60,13 @@ options:
58
60
  import { middlewareBarrel } from '@kubb/middleware-barrel'
59
61
 
60
62
  export default defineConfig({
61
- input: { path: './petstore.yaml' },
63
+ input: { path: './petStore.yaml' },
62
64
  output: { path: './src/gen', barrel: { type: 'named' } },
63
65
  middleware: [middlewareBarrel()],
66
+ plugins: [],
64
67
  })
65
68
  examples:
66
- - name: "barrel: { type: 'named' } (default)"
69
+ - name: "{ type: 'named' } (default)"
67
70
  files:
68
71
  - name: kubb.config.ts
69
72
  lang: typescript
@@ -72,10 +75,11 @@ examples:
72
75
  import { defineConfig } from 'kubb'
73
76
 
74
77
  export default defineConfig({
75
- input: { path: './petstore.yaml' },
78
+ input: { path: './petStore.yaml' },
76
79
  output: { path: './src/gen' },
80
+ plugins: [],
77
81
  })
78
- - name: 'Generated output'
82
+ - name: Generated output
79
83
  lang: typescript
80
84
  twoslash: false
81
85
  code: |-
@@ -92,7 +96,7 @@ examples:
92
96
 
93
97
  // src/gen/api/types/index.ts
94
98
  export { User } from './User'
95
- - name: "barrel: { type: 'all' }"
99
+ - name: "{ type: 'all' }"
96
100
  files:
97
101
  - name: kubb.config.ts
98
102
  lang: typescript
@@ -101,10 +105,11 @@ examples:
101
105
  import { defineConfig } from 'kubb'
102
106
 
103
107
  export default defineConfig({
104
- input: { path: './petstore.yaml' },
108
+ input: { path: './petStore.yaml' },
105
109
  output: { path: './src/gen', barrel: { type: 'all' } },
110
+ plugins: [],
106
111
  })
107
- - name: 'Generated output'
112
+ - name: Generated output
108
113
  lang: typescript
109
114
  twoslash: false
110
115
  code: |-
@@ -121,7 +126,7 @@ examples:
121
126
 
122
127
  // src/gen/api/types/index.ts
123
128
  export * from './User'
124
- - name: "barrel: { type: 'named', nested: true }"
129
+ - name: "{ type: 'named', nested: true }"
125
130
  files:
126
131
  - name: kubb.config.ts
127
132
  lang: typescript
@@ -130,10 +135,11 @@ examples:
130
135
  import { defineConfig } from 'kubb'
131
136
 
132
137
  export default defineConfig({
133
- input: { path: './petstore.yaml' },
138
+ input: { path: './petStore.yaml' },
134
139
  output: { path: './src/gen', barrel: { type: 'named', nested: true } },
140
+ plugins: [],
135
141
  })
136
- - name: 'Generated output (chained structure)'
142
+ - name: Generated output (chained structure)
137
143
  lang: typescript
138
144
  twoslash: false
139
145
  code: |-
@@ -148,7 +154,7 @@ examples:
148
154
 
149
155
  // src/gen/api/types/index.ts (exports files)
150
156
  export * from './User'
151
- - name: Disable barrel for a single plugin
157
+ - name: Disable the barrel for a single plugin
152
158
  files:
153
159
  - name: kubb.config.ts
154
160
  lang: typescript
@@ -158,17 +164,17 @@ examples:
158
164
  import { pluginTs } from '@kubb/plugin-ts'
159
165
  import { pluginZod } from '@kubb/plugin-zod'
160
166
 
161
- // pluginZod opts out: no zod/index.ts is created
167
+ // pluginZod opts out: no zod/index.ts is created,
162
168
  // and zod files are excluded from the root index.ts.
163
169
  export default defineConfig({
164
- input: { path: './petstore.yaml' },
170
+ input: { path: './petStore.yaml' },
165
171
  output: { path: './src/gen' },
166
172
  plugins: [
167
173
  pluginTs(),
168
174
  pluginZod({ output: { barrel: false } }),
169
175
  ],
170
176
  })
171
- - name: Disable the root barrel only
177
+ - name: Disable only the root barrel
172
178
  files:
173
179
  - name: kubb.config.ts
174
180
  lang: typescript
@@ -177,15 +183,16 @@ examples:
177
183
  import { defineConfig } from 'kubb'
178
184
 
179
185
  // No root index.ts is generated, but each plugin
180
- // still gets its own barrel using its inherited barrel config.
186
+ // still gets its own barrel using its inherited config.
181
187
  export default defineConfig({
182
- input: { path: './petstore.yaml' },
188
+ input: { path: './petStore.yaml' },
183
189
  output: { path: './src/gen', barrel: false },
190
+ plugins: [],
184
191
  })
185
192
  notes:
186
193
  - type: tip
187
194
  body: |-
188
- `middleware-barrel` is bundled with Kubb and enabled automatically when no `middleware` option is provided. You only need to install it explicitly when customising the barrel behaviour alongside other middlewares.
195
+ `middleware-barrel` ships with Kubb and is enabled automatically when no `middleware` option is provided. Install it explicitly only when customizing barrel behavior alongside other middlewares.
189
196
  - type: note
190
197
  body: |-
191
- `output.barrel` is only auto-defaulted to `{ type: 'named' }` when `middlewareBarrel` is part of `config.middleware`. If you provide a custom middleware list without it, set `output.barrel` yourself if you still want a barrel.
198
+ `output.barrel` is auto-defaulted to `{ type: 'named' }` only when `middlewareBarrel` is part of `config.middleware`. With a custom middleware list that omits it, set `output.barrel` yourself if you still want a barrel.
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "@kubb/middleware-barrel",
3
- "version": "5.0.0-beta.5",
4
- "description": "Barrel-file generation middleware for Kubb. Generates index.ts barrel files for each plugin's output directory and a root index.ts after all plugins have run.",
3
+ "version": "5.0.0-beta.50",
4
+ "description": "Barrel-file middleware for Kubb. Automatically generates index.ts re-export files per plugin output directory and an optional root barrel after all plugins complete.",
5
5
  "keywords": [
6
6
  "barrel",
7
- "code-generator",
8
7
  "codegen",
9
8
  "kubb",
10
9
  "middleware",
11
- "openapi",
12
10
  "typescript"
13
11
  ],
14
12
  "license": "MIT",
@@ -44,21 +42,26 @@
44
42
  "access": "public",
45
43
  "registry": "https://registry.npmjs.org/"
46
44
  },
45
+ "dependencies": {
46
+ "@kubb/ast": "5.0.0-beta.50",
47
+ "@kubb/core": "5.0.0-beta.50"
48
+ },
47
49
  "devDependencies": {
48
50
  "@internals/utils": "0.0.0"
49
51
  },
50
52
  "peerDependencies": {
51
- "@kubb/core": "5.0.0-beta.5"
53
+ "@kubb/core": "5.0.0-beta.50"
52
54
  },
53
55
  "engines": {
54
56
  "node": ">=22"
55
57
  },
56
58
  "scripts": {
57
59
  "build": "tsdown",
58
- "clean": "npx rimraf ./dist",
60
+ "clean": "node -e \"require('node:fs').rmSync('./dist', {recursive:true,force:true})\"",
59
61
  "lint": "oxlint .",
60
62
  "lint:fix": "oxlint --fix .",
61
63
  "release": "pnpm publish --no-git-check",
64
+ "release:stage": "pnpm stage publish --no-git-check",
62
65
  "start": "tsdown --watch",
63
66
  "test": "vitest --passWithNoTests",
64
67
  "typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false"
package/src/middleware.ts CHANGED
@@ -1,10 +1,34 @@
1
1
  import path from 'node:path'
2
2
  import { resolve } from 'node:path'
3
+ import type { FileNode } from '@kubb/ast'
3
4
  import { defineMiddleware } from '@kubb/core'
4
- import type { Middleware } from '@kubb/core'
5
+ import type { Config, Middleware, NormalizedPlugin } from '@kubb/core'
5
6
  import type { BarrelConfig, PluginBarrelConfig } from './types.ts'
6
- import { getPluginOutputPrefix, isExcludedPath } from './utils/excludedPaths.ts'
7
- import { getBarrelFiles } from './utils/getBarrelFiles.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
+ }
8
32
 
9
33
  declare global {
10
34
  namespace Kubb {
@@ -12,7 +36,7 @@ declare global {
12
36
  output: {
13
37
  /**
14
38
  * Barrel configuration for this plugin's output.
15
- * Set to `false` to disable barrel generation for this plugin entirely; doing so also
39
+ * Set to `false` to disable barrel generation for this plugin entirely. Doing so also
16
40
  * excludes the plugin's files from the root barrel.
17
41
  *
18
42
  * Falls back to `config.output.barrel` when omitted.
@@ -38,17 +62,32 @@ declare global {
38
62
  }
39
63
 
40
64
  /**
41
- * Generates `index.ts` barrel files for each plugin and a root barrel at `config.output.path/index.ts`.
65
+ * Canonical middleware name for `@kubb/middleware-barrel`. Used for driver
66
+ * lookups.
67
+ */
68
+ export const middlewareBarrelName = 'middleware-barrel' satisfies Middleware['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.
42
78
  *
43
- * Each plugin inherits `output.barrel` from `config.output.barrel` (defaults to `{ type: 'named' }`).
44
- * Set `barrel: false` on a plugin to disable its barrel and exclude it from the root barrel.
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.
45
81
  *
46
82
  * @example
47
83
  * ```ts
48
84
  * import { defineConfig } from '@kubb/core'
49
85
  * import { middlewareBarrel } from '@kubb/middleware-barrel'
86
+ * import { pluginTs } from '@kubb/plugin-ts'
87
+ * import { pluginZod } from '@kubb/plugin-zod'
50
88
  *
51
89
  * export default defineConfig({
90
+ * input: { path: './petStore.yaml' },
52
91
  * output: { path: 'src/gen', barrel: { type: 'named' } },
53
92
  * plugins: [
54
93
  * pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
@@ -58,12 +97,6 @@ declare global {
58
97
  * })
59
98
  * ```
60
99
  */
61
-
62
- /**
63
- * Stable string identifier for the barrel middleware.
64
- */
65
- export const middlewareBarrelName = 'middleware-barrel' satisfies Middleware['name']
66
-
67
100
  export const middlewareBarrel = defineMiddleware(() => {
68
101
  const excludedPrefixes = new Set<string>()
69
102
 
@@ -75,21 +108,24 @@ export const middlewareBarrel = defineMiddleware(() => {
75
108
  const configBarrel = config.output.barrel
76
109
  const defaultBarrel = { type: 'named' } as const
77
110
 
78
- let barrelConfig: PluginBarrelConfig | false
79
- if (pluginBarrel !== undefined) {
80
- barrelConfig = pluginBarrel
81
- } else if (configBarrel !== undefined) {
82
- // Root config barrel doesn't have nested, so we add it
83
- barrelConfig = configBarrel === false ? false : { ...configBarrel, nested: false }
84
- } else {
85
- barrelConfig = defaultBarrel
86
- }
111
+ // Root config barrel doesn't have nested, so we add it
112
+ const barrelConfig: PluginBarrelConfig | false = (() => {
113
+ if (pluginBarrel !== undefined) return pluginBarrel
114
+ if (configBarrel !== undefined) return configBarrel === false ? false : { ...configBarrel, nested: false }
115
+ return defaultBarrel
116
+ })()
87
117
 
88
118
  if (barrelConfig === false) {
89
119
  excludedPrefixes.add(getPluginOutputPrefix(plugin, config))
90
120
  return
91
121
  }
92
122
 
123
+ // `mode: 'file'` writes a single file, so there is no directory to barrel. The root barrel
124
+ // re-exports that file as a direct leaf of `config.output.path`.
125
+ if (plugin.options.output.mode === 'file') {
126
+ return
127
+ }
128
+
93
129
  const barrelType = barrelConfig.type
94
130
  const nested = barrelConfig.nested ?? false
95
131
 
@@ -99,16 +135,8 @@ export const middlewareBarrel = defineMiddleware(() => {
99
135
  if (relative.startsWith('..') || path.isAbsolute(relative)) {
100
136
  throw new Error('Invalid output path')
101
137
  }
102
- const barrelFiles = getBarrelFiles({
103
- outputPath: target,
104
- files,
105
- barrelType,
106
- nested,
107
- recursive: true,
108
- })
109
-
110
- if (barrelFiles.length > 0) {
111
- upsertFile(...barrelFiles)
138
+ for (const file of getBarrelFiles({ outputPath: target, files, barrelType, nested, recursive: true })) {
139
+ upsertFile(withBarrelBannerFooter({ file, plugin, config }))
112
140
  }
113
141
  },
114
142
  'kubb:plugins:end'({ files, config, upsertFile }) {
@@ -121,14 +149,8 @@ export const middlewareBarrel = defineMiddleware(() => {
121
149
 
122
150
  const barrelType = barrelConfig.type
123
151
 
124
- const rootBarrelFiles = getBarrelFiles({
125
- outputPath: resolve(config.root, config.output.path),
126
- files: filteredFiles,
127
- barrelType,
128
- })
129
-
130
- if (rootBarrelFiles.length > 0) {
131
- upsertFile(...rootBarrelFiles)
152
+ for (const file of getBarrelFiles({ outputPath: resolve(config.root, config.output.path), files: filteredFiles, barrelType })) {
153
+ upsertFile(file)
132
154
  }
133
155
  },
134
156
  },
package/src/types.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Barrel export strategy.
3
3
  *
4
- * - `'all'` generates `export * from '...'` for every file
5
- * - `'named'` generates `export { name1, name2 } from '...'` using each file's named exports
4
+ * - `'all'` generates `export * from '...'` for every file
5
+ * - `'named'` generates `export { name1, name2 } from '...'` using each file's named exports
6
6
  */
7
7
  export type BarrelType = 'all' | 'named'
8
8
 
@@ -19,8 +19,8 @@ export type BarrelType = 'all' | 'named'
19
19
  export type BarrelConfig = {
20
20
  /**
21
21
  * Export strategy for the root barrel file.
22
- * - `'all'` wildcard exports: `export * from './file'`
23
- * - `'named'` explicit exports: `export { x, y } from './file'`
22
+ * - `'all'` wildcard exports: `export * from './file'`
23
+ * - `'named'` explicit exports: `export { x, y } from './file'`
24
24
  */
25
25
  type: BarrelType
26
26
  }
@@ -40,8 +40,8 @@ export type BarrelConfig = {
40
40
  export type PluginBarrelConfig = {
41
41
  /**
42
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'`
43
+ * - `'all'` wildcard exports: `export * from './file'`
44
+ * - `'named'` explicit exports: `export { x, y } from './file'`
45
45
  */
46
46
  type: BarrelType
47
47
  /**
@@ -1,12 +1,12 @@
1
- import { extname } from 'node:path'
1
+ import { extname, resolve } from 'node:path'
2
2
  import { createExport, createFile } from '@kubb/ast'
3
3
  import type { ExportNode, FileNode, SourceNode } from '@kubb/ast'
4
- import { BARREL_FILENAME } from '../constants.ts'
5
- import type { BarrelType } from '../types.ts'
4
+ import type { Config, NormalizedPlugin } from '@kubb/core'
6
5
  import { type BuildTree, buildTree, toPosixPath } from '@internals/utils'
6
+ import type { BarrelType } from './types.ts'
7
7
 
8
8
  const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])
9
- const BARREL_SUFFIX = `/${BARREL_FILENAME}`
9
+ const BARREL_SUFFIX = `/index.ts`
10
10
 
11
11
  function toRelativeModulePath(fromDir: string, filePath: string): string {
12
12
  return `./${filePath.slice(fromDir.length + 1)}`
@@ -18,13 +18,14 @@ function isBarrelPath(path: string): boolean {
18
18
 
19
19
  function makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {
20
20
  return createFile({
21
- baseName: BARREL_FILENAME,
21
+ baseName: 'index.ts',
22
22
  path: `${dirPath}${BARREL_SUFFIX}`,
23
23
  exports,
24
24
  sources: [],
25
25
  imports: [],
26
- // Barrel files must never carry a banner or footer: they only re-export
27
- // symbols and adding a directive like "use server" would break consumers.
26
+ // Default to no banner/footer. The middleware 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".
28
29
  banner: undefined,
29
30
  footer: undefined,
30
31
  })
@@ -33,7 +34,7 @@ function makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {
33
34
  type LeafContext = {
34
35
  dirPath: string
35
36
  leafPath: string
36
- sourceFile: FileNode | undefined
37
+ sourceFile: FileNode | null
37
38
  }
38
39
 
39
40
  type LeafStrategy = (ctx: LeafContext) => Array<ExportNode>
@@ -99,9 +100,10 @@ type LeafWalkParams = {
99
100
  }
100
101
 
101
102
  /**
102
- * Post-order walk that emits a barrel per visited directory.
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).
103
105
  */
104
- function walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean, out: Array<FileNode>): Array<string> {
106
+ function* walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean): Generator<FileNode, Array<string>> {
105
107
  const subtreeLeaves: Array<string> = []
106
108
 
107
109
  for (const child of node.children) {
@@ -110,26 +112,26 @@ function walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean
110
112
  continue
111
113
  }
112
114
 
113
- const childLeaves = walkAllOrNamed(child, params, false, out)
115
+ const childLeaves = yield* walkAllOrNamed(child, params, false)
114
116
  for (const leaf of childLeaves) subtreeLeaves.push(leaf)
115
117
  }
116
118
 
117
119
  if (!isRoot && !params.recursive) return subtreeLeaves
118
120
 
119
- const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))
121
+ const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) ?? null }))
120
122
 
121
123
  if (exports.length > 0) {
122
- out.push(makeBarrel(node.path, exports))
124
+ yield makeBarrel(node.path, exports)
123
125
  }
124
126
 
125
127
  return subtreeLeaves
126
128
  }
127
129
 
128
130
  /**
129
- * Recursive walk that emits one barrel per directory, re-exporting files and sub-barrels.
131
+ * Recursive walk that yields one barrel per directory, re-exporting files and sub-barrels.
130
132
  * Used when nested: true.
131
133
  */
132
- function walkNested(node: BuildTree, out: Array<FileNode>): void {
134
+ function* walkNested(node: BuildTree): Generator<FileNode> {
133
135
  const exports: Array<ExportNode> = []
134
136
 
135
137
  for (const child of node.children) {
@@ -139,12 +141,12 @@ function walkNested(node: BuildTree, out: Array<FileNode>): void {
139
141
  continue
140
142
  }
141
143
 
142
- walkNested(child, out)
144
+ yield* walkNested(child)
143
145
  exports.push(createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))
144
146
  }
145
147
 
146
148
  if (exports.length > 0) {
147
- out.push(makeBarrel(node.path, exports))
149
+ yield makeBarrel(node.path, exports)
148
150
  }
149
151
  }
150
152
 
@@ -204,24 +206,53 @@ type GetBarrelFilesParams = {
204
206
  }
205
207
 
206
208
  /**
207
- * Generates barrel `FileNode`s for the directory rooted at `outputPath`.
209
+ * Yields barrel `FileNode`s for the directory rooted at `outputPath`.
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * for (const file of getBarrelFiles({ outputPath, files, barrelType })) {
214
+ * upsertFile(file)
215
+ * }
216
+ * // or collect into an array
217
+ * const barrels = [...getBarrelFiles({ outputPath, files, barrelType })]
218
+ * ```
208
219
  */
209
- export function getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Array<FileNode> {
220
+ export function* getBarrelFiles({ outputPath, files, barrelType, nested = false, recursive = false }: GetBarrelFilesParams): Generator<FileNode> {
210
221
  const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)
211
- if (paths.length === 0) return []
222
+ if (paths.length === 0) return
212
223
 
213
224
  const tree = buildTree(outputPath, paths)
214
- const result: Array<FileNode> = []
215
225
 
216
- // Use nested walk for hierarchical barrel structure
217
226
  if (nested) {
218
- walkNested(tree, result)
219
- return result
227
+ yield* walkNested(tree)
228
+ return
220
229
  }
221
230
 
222
231
  const strategy = LEAF_STRATEGIES.get(barrelType)
223
- if (!strategy) return result
232
+ if (!strategy) return
224
233
 
225
- walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, true, result)
226
- return result
234
+ yield* walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, true)
235
+ }
236
+
237
+ /**
238
+ * Builds a POSIX-normalized prefix for a plugin's output. A directory output gets a trailing `/`,
239
+ * while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.
240
+ *
241
+ * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
242
+ */
243
+ export function getPluginOutputPrefix(plugin: NormalizedPlugin, config: Config): string {
244
+ const resolved = toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))
245
+ return plugin.options.output.mode === 'file' ? resolved : `${resolved}/`
246
+ }
247
+
248
+ /**
249
+ * Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a
250
+ * trailing `/` matches a directory subtree, and a prefix without one matches that exact file
251
+ * (used for `mode: 'file'` outputs).
252
+ *
253
+ * Both sides are POSIX-normalized so Windows backslash paths match correctly.
254
+ */
255
+ export function isExcludedPath(filePath: string, prefixes: ReadonlySet<string>): boolean {
256
+ const normalized = toPosixPath(filePath)
257
+ return prefixes.values().some((prefix) => (prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix))
227
258
  }
package/src/constants.ts DELETED
@@ -1,4 +0,0 @@
1
- /**
2
- * Full file name for barrel files (with extension).
3
- */
4
- export const BARREL_FILENAME = 'index.ts' as const
@@ -1,22 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import type { Config, NormalizedPlugin } from '@kubb/core'
3
- import { toPosixPath } from '@internals/utils'
4
-
5
- /**
6
- * Builds a POSIX-normalized prefix for a plugin's output directory, with a trailing `/`.
7
- *
8
- * Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
9
- */
10
- export function getPluginOutputPrefix(plugin: NormalizedPlugin, config: Config): string {
11
- return `${toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path))}/`
12
- }
13
-
14
- /**
15
- * Returns `true` when `filePath` lives under any of the given excluded directory prefixes.
16
- *
17
- * Both sides are POSIX-normalized so Windows backslash paths match correctly.
18
- */
19
- export function isExcludedPath(filePath: string, prefixes: ReadonlySet<string>): boolean {
20
- const normalized = toPosixPath(filePath)
21
- return prefixes.values().some((prefix) => normalized.startsWith(prefix))
22
- }
File without changes