@kubb/middleware-barrel 5.0.0-alpha.62 → 5.0.0-alpha.63

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.
@@ -7,81 +7,82 @@ import { buildTree, type BuildTree } from './buildTree.ts'
7
7
  const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])
8
8
 
9
9
  /**
10
- * Derives a relative module specifier from an absolute `filePath` relative to an absolute `fromDir`.
11
- * The source extension is preserved so that `@kubb/parser-ts` can apply the `extNames` mapping
12
- * (e.g. `.ts` → `.js` for ESM output).
10
+ * Derives a relative module specifier from `filePath` relative to `fromDir`.
11
+ * The source extension is preserved so `@kubb/parser-ts` can apply its `extNames` mapping.
13
12
  *
14
13
  * @example
14
+ * ```ts
15
15
  * toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet.ts'
16
16
  * toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag.ts'
17
+ * ```
17
18
  */
18
19
  function toRelativeModulePath(fromDir: string, filePath: string): string {
19
20
  const relative = filePath.slice(fromDir.length).replace(/^[/\\]/g, '')
20
21
  return `./${relative}`
21
22
  }
22
23
 
23
- /**
24
- * Generates barrel `FileNode[]` for a given directory tree node using the `'all'` strategy:
25
- * each leaf file gets a `export * from './relPath'` in the barrel of its nearest ancestor directory.
26
- *
27
- * Only a single barrel file (at `treeNode.path`) is generated — sub-directory files are referenced
28
- * with their full relative path from `treeNode.path`.
29
- */
30
- function getBarrelFilesAll(treeNode: BuildTree, sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {
31
- // Collect all source file paths under this node (excluding barrel files themselves)
24
+ type BarrelFilesParams = {
25
+ treeNode: BuildTree
26
+ sourceFiles: ReadonlyArray<FileNode>
27
+ recursive?: boolean
28
+ }
29
+
30
+ function getBarrelFilesAll({ treeNode, sourceFiles, recursive = false }: BarrelFilesParams): Array<FileNode> {
32
31
  const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))
33
32
 
34
33
  if (leafPaths.length === 0) return []
35
34
 
36
- const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`
37
35
  const exports: ReturnType<typeof createExport>[] = []
38
36
 
39
37
  for (const filePath of leafPaths) {
40
38
  const sourceFile = sourceFiles.find((f) => f.path === filePath)
41
- // Skip files whose sources all have isIndexable: false (e.g. internal injected files)
42
39
  if (sourceFile && sourceFile.sources.length > 0 && sourceFile.sources.every((s) => !s.isIndexable)) {
43
40
  continue
44
41
  }
45
42
  exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))
46
43
  }
47
44
 
48
- if (exports.length === 0) return []
45
+ const result: Array<FileNode> = []
46
+
47
+ if (recursive) {
48
+ for (const child of treeNode.children) {
49
+ if (!child.isFile) {
50
+ result.push(...getBarrelFilesAll({ treeNode: child, sourceFiles, recursive: true }))
51
+ }
52
+ }
53
+ }
54
+
55
+ if (exports.length === 0) return result
49
56
 
50
- return [
57
+ result.push(
51
58
  createFile({
52
59
  baseName: BARREL_FILENAME,
53
- path: barrelPath,
60
+ path: `${treeNode.path}/${BARREL_FILENAME}`,
54
61
  exports,
55
62
  sources: [],
56
63
  imports: [],
57
64
  }),
58
- ]
65
+ )
66
+
67
+ return result
59
68
  }
60
69
 
61
- /**
62
- * Generates barrel `FileNode[]` for a given directory tree node using the `'named'` strategy:
63
- * each indexable source in each leaf file gets an individual named `export { name } from '...'`.
64
- */
65
- function getBarrelFilesNamed(treeNode: BuildTree, sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {
70
+ function getBarrelFilesNamed({ treeNode, sourceFiles, recursive = false }: BarrelFilesParams): Array<FileNode> {
66
71
  const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))
67
72
 
68
73
  if (leafPaths.length === 0) return []
69
74
 
70
- const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`
71
75
  const exports: ReturnType<typeof createExport>[] = []
72
76
 
73
77
  for (const filePath of leafPaths) {
74
78
  const sourceFile = sourceFiles.find((f) => f.path === filePath)
75
79
  if (!sourceFile) {
76
- // Fall back to wildcard if the source file is not in our set
77
80
  exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))
78
81
  continue
79
82
  }
80
83
 
81
84
  const indexableSources = sourceFile.sources.filter((s) => s.isIndexable && s.name)
82
85
  if (indexableSources.length === 0) {
83
- // If the file has explicit sources but none are indexable, skip it entirely.
84
- // Only fall back to wildcard when there are no sources at all (unknown exports).
85
86
  if (sourceFile.sources.length > 0) continue
86
87
  exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))
87
88
  continue
@@ -99,27 +100,32 @@ function getBarrelFilesNamed(treeNode: BuildTree, sourceFiles: ReadonlyArray<Fil
99
100
  }
100
101
  }
101
102
 
102
- if (exports.length === 0) return []
103
+ const result: Array<FileNode> = []
103
104
 
104
- return [
105
- createFile({
106
- baseName: BARREL_FILENAME,
107
- path: barrelPath,
108
- exports,
109
- sources: [],
110
- imports: [],
111
- }),
112
- ]
105
+ if (recursive) {
106
+ for (const child of treeNode.children) {
107
+ if (!child.isFile) {
108
+ result.push(...getBarrelFilesNamed({ treeNode: child, sourceFiles, recursive: true }))
109
+ }
110
+ }
111
+ }
112
+
113
+ if (exports.length > 0) {
114
+ result.push(
115
+ createFile({
116
+ baseName: BARREL_FILENAME,
117
+ path: `${treeNode.path}/${BARREL_FILENAME}`,
118
+ exports,
119
+ sources: [],
120
+ imports: [],
121
+ }),
122
+ )
123
+ }
124
+
125
+ return result
113
126
  }
114
127
 
115
- /**
116
- * Generates barrel `FileNode[]` for a given directory tree node using the `'propagate'` strategy:
117
- * like `'all'` but also generates intermediate barrel files for every sub-directory, so that
118
- * consumers can import from any depth.
119
- *
120
- * Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.
121
- */
122
- function getBarrelFilesPropagate(treeNode: BuildTree): Array<FileNode> {
128
+ function getBarrelFilesPropagate({ treeNode }: Pick<BarrelFilesParams, 'treeNode'>): Array<FileNode> {
123
129
  return collectPropagatedBarrels(treeNode)
124
130
  }
125
131
 
@@ -133,11 +139,9 @@ function collectPropagatedBarrels(node: BuildTree): Array<FileNode> {
133
139
  barrelExports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }))
134
140
  }
135
141
  } else {
136
- // Recurse into sub-directory
137
142
  const subBarrels = collectPropagatedBarrels(child)
138
143
  result.push(...subBarrels)
139
144
 
140
- // Export the sub-directory's barrel (not individual files)
141
145
  const subBarrelPath = `${child.path}/${BARREL_FILENAME}`
142
146
  barrelExports.push(createExport({ path: toRelativeModulePath(node.path, subBarrelPath) }))
143
147
  }
@@ -158,26 +162,44 @@ function collectPropagatedBarrels(node: BuildTree): Array<FileNode> {
158
162
  return result
159
163
  }
160
164
 
161
- /**
162
- * Collects all leaf (file) paths within a tree node recursively.
163
- */
164
165
  function collectLeafPaths(node: BuildTree): Array<string> {
165
166
  if (node.isFile) return [node.path]
166
167
  return node.children.flatMap((c) => collectLeafPaths(c))
167
168
  }
168
169
 
170
+ export type GetBarrelFilesParams = {
171
+ /**
172
+ * Absolute path to the directory the barrel(s) should be rooted at.
173
+ * Files outside this directory are ignored.
174
+ */
175
+ outputPath: string
176
+ /**
177
+ * Full set of generated files across all plugins.
178
+ * Used both to discover what to re-export and to read each file's indexable sources.
179
+ */
180
+ files: ReadonlyArray<FileNode>
181
+ /**
182
+ * Re-export style used in the generated barrel(s).
183
+ */
184
+ barrelType: BarrelType
185
+ /**
186
+ * When `true`, also generate a barrel for each sub-directory of `outputPath`.
187
+ * Used by per-plugin barrels so that grouped output (e.g. `petController/`) gets its own `index.ts`.
188
+ *
189
+ * Has no effect for `barrelType: 'propagate'`, which always recurses by design.
190
+ *
191
+ * @default false
192
+ */
193
+ recursive?: boolean
194
+ }
195
+
169
196
  /**
170
- * Generates barrel `FileNode[]` for a directory rooted at `outputPath`, given the full set of
171
- * generated source `files`, using the specified `barrelType` strategy.
172
- *
173
- * Files not located inside `outputPath` are excluded automatically.
197
+ * Generates barrel `FileNode`s for the directory rooted at `outputPath`.
174
198
  *
175
- * @param outputPath Absolute path to the output directory.
176
- * @param files All generated files (across all plugins).
177
- * @param barrelType Barrel generation strategy.
199
+ * Files outside `outputPath`, existing barrel files, and non-source extensions are filtered out
200
+ * before the tree is built.
178
201
  */
179
- export function getBarrelFiles(outputPath: string, files: ReadonlyArray<FileNode>, barrelType: BarrelType): Array<FileNode> {
180
- // Only include files that live inside this outputPath and have a recognised source extension
202
+ export function getBarrelFiles({ outputPath, files, barrelType, recursive = false }: GetBarrelFilesParams): Array<FileNode> {
181
203
  const relevantFiles = files.filter((f) => {
182
204
  const normalizedFilePath = f.path.replace(/\\/g, '/')
183
205
  const normalizedOutputPath = outputPath.replace(/\\/g, '/')
@@ -197,11 +219,11 @@ export function getBarrelFiles(outputPath: string, files: ReadonlyArray<FileNode
197
219
 
198
220
  switch (barrelType) {
199
221
  case 'all':
200
- return getBarrelFilesAll(tree, relevantFiles)
222
+ return getBarrelFilesAll({ treeNode: tree, sourceFiles: relevantFiles, recursive })
201
223
  case 'named':
202
- return getBarrelFilesNamed(tree, relevantFiles)
224
+ return getBarrelFilesNamed({ treeNode: tree, sourceFiles: relevantFiles, recursive })
203
225
  case 'propagate':
204
- return getBarrelFilesPropagate(tree)
226
+ return getBarrelFilesPropagate({ treeNode: tree })
205
227
  default:
206
228
  return []
207
229
  }