@kubb/middleware-barrel 5.0.0-alpha.63 → 5.0.0-alpha.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 +185 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +186 -127
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/middleware.ts +10 -7
- package/src/utils/excludedPaths.ts +23 -0
- package/src/utils/getBarrelFiles.ts +142 -135
- package/src/utils/resolveBarrelType.ts +20 -0
- package/src/utils/buildTree.ts +0 -63
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { extname } from 'node:path'
|
|
1
2
|
import { createExport, createFile } from '@kubb/ast'
|
|
2
|
-
import type { FileNode } from '@kubb/ast'
|
|
3
|
+
import type { ExportNode, FileNode, SourceNode } from '@kubb/ast'
|
|
3
4
|
import { BARREL_FILENAME } from '../constants.ts'
|
|
4
5
|
import type { BarrelType } from '../types.ts'
|
|
5
|
-
import {
|
|
6
|
+
import { type BuildTree, buildTree } from '@internals/utils'
|
|
6
7
|
|
|
7
8
|
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])
|
|
9
|
+
const BARREL_SUFFIX = `/${BARREL_FILENAME}`
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Derives a relative module specifier from `filePath` relative to `fromDir`.
|
|
@@ -17,154 +19,170 @@ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])
|
|
|
17
19
|
* ```
|
|
18
20
|
*/
|
|
19
21
|
function toRelativeModulePath(fromDir: string, filePath: string): string {
|
|
20
|
-
|
|
21
|
-
return `./${relative}`
|
|
22
|
+
return `./${filePath.slice(fromDir.length + 1)}`
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
sourceFiles: ReadonlyArray<FileNode>
|
|
27
|
-
recursive?: boolean
|
|
25
|
+
function isBarrelPath(path: string): boolean {
|
|
26
|
+
return path.endsWith(BARREL_SUFFIX)
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
function
|
|
31
|
-
|
|
29
|
+
function makeBarrel(dirPath: string, exports: Array<ExportNode>): FileNode {
|
|
30
|
+
return createFile({
|
|
31
|
+
baseName: BARREL_FILENAME,
|
|
32
|
+
path: `${dirPath}${BARREL_SUFFIX}`,
|
|
33
|
+
exports,
|
|
34
|
+
sources: [],
|
|
35
|
+
imports: [],
|
|
36
|
+
})
|
|
37
|
+
}
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
type LeafContext = {
|
|
40
|
+
dirPath: string
|
|
41
|
+
leafPath: string
|
|
42
|
+
sourceFile: FileNode | undefined
|
|
43
|
+
}
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
type LeafStrategy = (ctx: LeafContext) => Array<ExportNode>
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))
|
|
47
|
+
function hasOnlyNonIndexableSources(sources: ReadonlyArray<SourceNode>): boolean {
|
|
48
|
+
if (sources.length === 0) return false
|
|
49
|
+
for (const source of sources) {
|
|
50
|
+
if (source.isIndexable) return false
|
|
43
51
|
}
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
function partitionIndexableNames(sources: ReadonlyArray<SourceNode>): Map<boolean, Set<string>> {
|
|
56
|
+
const byTypeOnly = new Map<boolean, Set<string>>([
|
|
57
|
+
[false, new Set()],
|
|
58
|
+
[true, new Set()],
|
|
59
|
+
])
|
|
60
|
+
for (const source of sources) {
|
|
61
|
+
if (!source.isIndexable || !source.name) continue
|
|
62
|
+
byTypeOnly.get(Boolean(source.isTypeOnly))!.add(source.name)
|
|
53
63
|
}
|
|
64
|
+
return byTypeOnly
|
|
65
|
+
}
|
|
54
66
|
|
|
55
|
-
|
|
67
|
+
const allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
68
|
+
if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return []
|
|
69
|
+
return [createExport({ path: toRelativeModulePath(dirPath, leafPath) })]
|
|
70
|
+
}
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
baseName: BARREL_FILENAME,
|
|
60
|
-
path: `${treeNode.path}/${BARREL_FILENAME}`,
|
|
61
|
-
exports,
|
|
62
|
-
sources: [],
|
|
63
|
-
imports: [],
|
|
64
|
-
}),
|
|
65
|
-
)
|
|
72
|
+
const namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
73
|
+
const modulePath = toRelativeModulePath(dirPath, leafPath)
|
|
66
74
|
|
|
67
|
-
return
|
|
68
|
-
}
|
|
75
|
+
if (!sourceFile) return [createExport({ path: modulePath })]
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
const
|
|
77
|
+
const namesByTypeOnly = partitionIndexableNames(sourceFile.sources)
|
|
78
|
+
const valueNames = namesByTypeOnly.get(false)!
|
|
79
|
+
const typeNames = namesByTypeOnly.get(true)!
|
|
72
80
|
|
|
73
|
-
if (
|
|
81
|
+
if (valueNames.size === 0 && typeNames.size === 0) {
|
|
82
|
+
if (sourceFile.sources.length > 0) return []
|
|
83
|
+
return [createExport({ path: modulePath })]
|
|
84
|
+
}
|
|
74
85
|
|
|
75
|
-
const exports:
|
|
86
|
+
const exports: Array<ExportNode> = []
|
|
87
|
+
if (valueNames.size > 0) {
|
|
88
|
+
exports.push(createExport({ name: [...valueNames].sort(), path: modulePath }))
|
|
89
|
+
}
|
|
90
|
+
if (typeNames.size > 0) {
|
|
91
|
+
exports.push(createExport({ name: [...typeNames].sort(), path: modulePath, isTypeOnly: true }))
|
|
92
|
+
}
|
|
93
|
+
return exports
|
|
94
|
+
}
|
|
76
95
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
96
|
+
const LEAF_STRATEGIES: ReadonlyMap<Exclude<BarrelType, 'propagate'>, LeafStrategy> = new Map([
|
|
97
|
+
['all', allStrategy],
|
|
98
|
+
['named', namedStrategy],
|
|
99
|
+
])
|
|
83
100
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
101
|
+
type LeafWalkParams = {
|
|
102
|
+
sourceFiles: ReadonlyMap<string, FileNode>
|
|
103
|
+
strategy: LeafStrategy
|
|
104
|
+
recursive: boolean
|
|
105
|
+
}
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Single-pass post-order traversal that emits a barrel for each visited directory and
|
|
109
|
+
* returns its leaf paths so parents don't have to re-walk the subtree.
|
|
110
|
+
*/
|
|
111
|
+
function walkAllOrNamed(node: BuildTree, params: LeafWalkParams, isRoot: boolean, out: Array<FileNode>): Array<string> {
|
|
112
|
+
const subtreeLeaves: Array<string> = []
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
exports.push(createExport({ name: typeNames, path: modulePath, isTypeOnly: true }))
|
|
114
|
+
for (const child of node.children) {
|
|
115
|
+
if (child.isFile) {
|
|
116
|
+
if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)
|
|
117
|
+
continue
|
|
100
118
|
}
|
|
119
|
+
|
|
120
|
+
const childLeaves = walkAllOrNamed(child, params, false, out)
|
|
121
|
+
for (const leaf of childLeaves) subtreeLeaves.push(leaf)
|
|
101
122
|
}
|
|
102
123
|
|
|
103
|
-
|
|
124
|
+
// Sub-directory barrels are only emitted when the caller asked for them.
|
|
125
|
+
if (!isRoot && !params.recursive) return subtreeLeaves
|
|
104
126
|
|
|
105
|
-
|
|
106
|
-
for (const child of treeNode.children) {
|
|
107
|
-
if (!child.isFile) {
|
|
108
|
-
result.push(...getBarrelFilesNamed({ treeNode: child, sourceFiles, recursive: true }))
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
127
|
+
const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))
|
|
112
128
|
|
|
113
129
|
if (exports.length > 0) {
|
|
114
|
-
|
|
115
|
-
createFile({
|
|
116
|
-
baseName: BARREL_FILENAME,
|
|
117
|
-
path: `${treeNode.path}/${BARREL_FILENAME}`,
|
|
118
|
-
exports,
|
|
119
|
-
sources: [],
|
|
120
|
-
imports: [],
|
|
121
|
-
}),
|
|
122
|
-
)
|
|
130
|
+
out.push(makeBarrel(node.path, exports))
|
|
123
131
|
}
|
|
124
132
|
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function getBarrelFilesPropagate({ treeNode }: Pick<BarrelFilesParams, 'treeNode'>): Array<FileNode> {
|
|
129
|
-
return collectPropagatedBarrels(treeNode)
|
|
133
|
+
return subtreeLeaves
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Emits one barrel per directory: every direct child file is re-exported and every
|
|
138
|
+
* sub-directory is re-exported via its own barrel (recursive by design).
|
|
139
|
+
*/
|
|
140
|
+
function walkPropagate(node: BuildTree, out: Array<FileNode>): void {
|
|
141
|
+
const exports: Array<ExportNode> = []
|
|
135
142
|
|
|
136
143
|
for (const child of node.children) {
|
|
137
144
|
if (child.isFile) {
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
} else {
|
|
142
|
-
const subBarrels = collectPropagatedBarrels(child)
|
|
143
|
-
result.push(...subBarrels)
|
|
144
|
-
|
|
145
|
-
const subBarrelPath = `${child.path}/${BARREL_FILENAME}`
|
|
146
|
-
barrelExports.push(createExport({ path: toRelativeModulePath(node.path, subBarrelPath) }))
|
|
145
|
+
if (isBarrelPath(child.path)) continue
|
|
146
|
+
exports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }))
|
|
147
|
+
continue
|
|
147
148
|
}
|
|
149
|
+
|
|
150
|
+
walkPropagate(child, out)
|
|
151
|
+
exports.push(createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
createFile({
|
|
153
|
-
baseName: BARREL_FILENAME,
|
|
154
|
-
path: `${node.path}/${BARREL_FILENAME}`,
|
|
155
|
-
exports: barrelExports,
|
|
156
|
-
sources: [],
|
|
157
|
-
imports: [],
|
|
158
|
-
}),
|
|
159
|
-
)
|
|
154
|
+
if (exports.length > 0) {
|
|
155
|
+
out.push(makeBarrel(node.path, exports))
|
|
160
156
|
}
|
|
157
|
+
}
|
|
161
158
|
|
|
162
|
-
|
|
159
|
+
type IndexedFiles = {
|
|
160
|
+
/**
|
|
161
|
+
* `path → FileNode` lookup limited to files that participate in barrel generation.
|
|
162
|
+
*/
|
|
163
|
+
sourceFiles: ReadonlyMap<string, FileNode>
|
|
164
|
+
/**
|
|
165
|
+
* Original (un-normalized) paths of `sourceFiles`, in input order — used as input for {@link buildTree}.
|
|
166
|
+
*/
|
|
167
|
+
paths: ReadonlyArray<string>
|
|
163
168
|
}
|
|
164
169
|
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
function indexRelevantFiles(files: ReadonlyArray<FileNode>, outputPath: string): IndexedFiles {
|
|
171
|
+
const outputPrefix = `${outputPath.replaceAll('\\', '/')}/`
|
|
172
|
+
const sourceFiles = new Map<string, FileNode>()
|
|
173
|
+
const paths: Array<string> = []
|
|
174
|
+
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const normalized = file.path.replaceAll('\\', '/')
|
|
177
|
+
if (!normalized.startsWith(outputPrefix)) continue
|
|
178
|
+
if (isBarrelPath(normalized)) continue
|
|
179
|
+
if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue
|
|
180
|
+
|
|
181
|
+
sourceFiles.set(file.path, file)
|
|
182
|
+
paths.push(file.path)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { sourceFiles, paths }
|
|
168
186
|
}
|
|
169
187
|
|
|
170
188
|
export type GetBarrelFilesParams = {
|
|
@@ -200,31 +218,20 @@ export type GetBarrelFilesParams = {
|
|
|
200
218
|
* before the tree is built.
|
|
201
219
|
*/
|
|
202
220
|
export function getBarrelFiles({ outputPath, files, barrelType, recursive = false }: GetBarrelFilesParams): Array<FileNode> {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const dotIndex = normalizedFilePath.lastIndexOf('.')
|
|
209
|
-
const ext = dotIndex === -1 ? '' : normalizedFilePath.slice(dotIndex)
|
|
210
|
-
return SOURCE_EXTENSIONS.has(ext)
|
|
211
|
-
})
|
|
221
|
+
const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)
|
|
222
|
+
if (paths.length === 0) return []
|
|
223
|
+
|
|
224
|
+
const tree = buildTree(outputPath, paths)
|
|
225
|
+
const result: Array<FileNode> = []
|
|
212
226
|
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
outputPath,
|
|
217
|
-
relevantFiles.map((f) => f.path),
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
switch (barrelType) {
|
|
221
|
-
case 'all':
|
|
222
|
-
return getBarrelFilesAll({ treeNode: tree, sourceFiles: relevantFiles, recursive })
|
|
223
|
-
case 'named':
|
|
224
|
-
return getBarrelFilesNamed({ treeNode: tree, sourceFiles: relevantFiles, recursive })
|
|
225
|
-
case 'propagate':
|
|
226
|
-
return getBarrelFilesPropagate({ treeNode: tree })
|
|
227
|
-
default:
|
|
228
|
-
return []
|
|
227
|
+
if (barrelType === 'propagate') {
|
|
228
|
+
walkPropagate(tree, result)
|
|
229
|
+
return result
|
|
229
230
|
}
|
|
231
|
+
|
|
232
|
+
const strategy = LEAF_STRATEGIES.get(barrelType)
|
|
233
|
+
if (!strategy) return result
|
|
234
|
+
|
|
235
|
+
walkAllOrNamed(tree, { sourceFiles, strategy, recursive }, true, result)
|
|
236
|
+
return result
|
|
230
237
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Config, NormalizedPlugin } from '@kubb/core'
|
|
2
|
+
import type { BarrelType } from '../types.ts'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BARREL_TYPE: BarrelType = 'named'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the effective barrel style for a single plugin: explicit plugin option →
|
|
8
|
+
* root config option → `'named'` default. Returns `false` when barrel generation is disabled.
|
|
9
|
+
*/
|
|
10
|
+
export function resolvePluginBarrelType(plugin: NormalizedPlugin, config: Config): BarrelType | false {
|
|
11
|
+
return plugin.options.output?.barrelType ?? config.output.barrelType ?? DEFAULT_BARREL_TYPE
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the effective barrel style for the root `index.ts`: root config option → `'named'` default.
|
|
16
|
+
* Returns `false` when the root barrel is disabled.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveRootBarrelType(config: Config): BarrelType | false {
|
|
19
|
+
return config.output.barrelType ?? DEFAULT_BARREL_TYPE
|
|
20
|
+
}
|
package/src/utils/buildTree.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { posix } from 'node:path'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A node in the directory tree used to compute barrel file exports.
|
|
5
|
-
* Either represents a directory (with `children`) or a file (`isFile: true`, empty `children`).
|
|
6
|
-
*/
|
|
7
|
-
export type BuildTree = {
|
|
8
|
-
/**
|
|
9
|
-
* Absolute filesystem path of this directory or file.
|
|
10
|
-
*/
|
|
11
|
-
path: string
|
|
12
|
-
/**
|
|
13
|
-
* Sub-directories and files contained within this directory.
|
|
14
|
-
* Always empty for file nodes.
|
|
15
|
-
*/
|
|
16
|
-
children: Array<BuildTree>
|
|
17
|
-
/**
|
|
18
|
-
* `true` when this node represents a file (leaf), `false` for directory nodes.
|
|
19
|
-
*/
|
|
20
|
-
isFile: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Builds a directory tree rooted at `rootPath` from a list of absolute file paths.
|
|
25
|
-
* Paths outside `rootPath` are silently ignored.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* buildTree('/src/gen/types', [
|
|
30
|
-
* '/src/gen/types/pet.ts',
|
|
31
|
-
* '/src/gen/types/pets/listPets.ts',
|
|
32
|
-
* ])
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
export function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {
|
|
36
|
-
const root: BuildTree = { path: rootPath, children: [], isFile: false }
|
|
37
|
-
|
|
38
|
-
for (const filePath of filePaths) {
|
|
39
|
-
// Only include files inside rootPath
|
|
40
|
-
if (!filePath.startsWith(rootPath + posix.sep) && !filePath.startsWith(rootPath + '/')) {
|
|
41
|
-
continue
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const relative = filePath.slice(rootPath.length).replace(/^\//g, '').replace(/^\\/g, '')
|
|
45
|
-
const parts = relative.split(/[/\\]/).filter(Boolean)
|
|
46
|
-
|
|
47
|
-
let current = root
|
|
48
|
-
for (let i = 0; i < parts.length; i++) {
|
|
49
|
-
const isLast = i === parts.length - 1
|
|
50
|
-
const part = parts[i]!
|
|
51
|
-
const childPath = `${current.path}/${part}`
|
|
52
|
-
|
|
53
|
-
let child = current.children.find((c) => c.path === childPath)
|
|
54
|
-
if (!child) {
|
|
55
|
-
child = { path: childPath, children: [], isFile: isLast }
|
|
56
|
-
current.children.push(child)
|
|
57
|
-
}
|
|
58
|
-
current = child
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return root
|
|
63
|
-
}
|