@kubb/middleware-barrel 5.0.0-alpha.62 → 5.0.0-alpha.64
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 +213 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +20 -29
- package/dist/index.js +214 -160
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/constants.ts +0 -1
- package/src/middleware.ts +23 -33
- package/src/types.ts +5 -7
- package/src/utils/excludedPaths.ts +23 -0
- package/src/utils/generatePerPluginBarrel.ts +17 -7
- package/src/utils/generateRootBarrel.ts +14 -7
- package/src/utils/getBarrelFiles.ts +183 -154
- package/src/utils/resolveBarrelType.ts +20 -0
- package/src/utils/buildTree.ts +0 -67
|
@@ -1,208 +1,237 @@
|
|
|
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
|
-
* Derives a relative module specifier from
|
|
11
|
-
* The source extension is preserved so
|
|
12
|
-
* (e.g. `.ts` → `.js` for ESM output).
|
|
12
|
+
* Derives a relative module specifier from `filePath` relative to `fromDir`.
|
|
13
|
+
* The source extension is preserved so `@kubb/parser-ts` can apply its `extNames` mapping.
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
16
|
+
* ```ts
|
|
15
17
|
* toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet.ts'
|
|
16
18
|
* toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag.ts'
|
|
19
|
+
* ```
|
|
17
20
|
*/
|
|
18
21
|
function toRelativeModulePath(fromDir: string, filePath: string): string {
|
|
19
|
-
|
|
20
|
-
return `./${relative}`
|
|
22
|
+
return `./${filePath.slice(fromDir.length + 1)}`
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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)
|
|
32
|
-
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))
|
|
25
|
+
function isBarrelPath(path: string): boolean {
|
|
26
|
+
return path.endsWith(BARREL_SUFFIX)
|
|
27
|
+
}
|
|
33
28
|
|
|
34
|
-
|
|
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
|
+
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
type LeafContext = {
|
|
40
|
+
dirPath: string
|
|
41
|
+
leafPath: string
|
|
42
|
+
sourceFile: FileNode | undefined
|
|
43
|
+
}
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))
|
|
45
|
+
type LeafStrategy = (ctx: LeafContext) => Array<ExportNode>
|
|
46
|
+
|
|
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
|
|
46
51
|
}
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
]
|
|
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)
|
|
63
|
+
}
|
|
64
|
+
return byTypeOnly
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
function getBarrelFilesNamed(treeNode: BuildTree, sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {
|
|
66
|
-
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))
|
|
67
|
+
const allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
68
|
+
if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return []
|
|
69
|
+
return [createExport({ path: toRelativeModulePath(dirPath, leafPath) })]
|
|
70
|
+
}
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
const namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
73
|
+
const modulePath = toRelativeModulePath(dirPath, leafPath)
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
const exports: ReturnType<typeof createExport>[] = []
|
|
75
|
+
if (!sourceFile) return [createExport({ path: modulePath })]
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
77
|
+
const namesByTypeOnly = partitionIndexableNames(sourceFile.sources)
|
|
78
|
+
const valueNames = namesByTypeOnly.get(false)!
|
|
79
|
+
const typeNames = namesByTypeOnly.get(true)!
|
|
80
|
+
|
|
81
|
+
if (valueNames.size === 0 && typeNames.size === 0) {
|
|
82
|
+
if (sourceFile.sources.length > 0) return []
|
|
83
|
+
return [createExport({ path: modulePath })]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const exports: Array<ExportNode> = []
|
|
87
|
+
if (valueNames.size > 0) {
|
|
88
|
+
exports.push(createExport({ name: [...valueNames], path: modulePath }))
|
|
89
|
+
}
|
|
90
|
+
if (typeNames.size > 0) {
|
|
91
|
+
exports.push(createExport({ name: [...typeNames], path: modulePath, isTypeOnly: true }))
|
|
92
|
+
}
|
|
93
|
+
return exports
|
|
94
|
+
}
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
const LEAF_STRATEGIES: ReadonlyMap<Exclude<BarrelType, 'propagate'>, LeafStrategy> = new Map([
|
|
97
|
+
['all', allStrategy],
|
|
98
|
+
['named', namedStrategy],
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
type LeafWalkParams = {
|
|
102
|
+
sourceFiles: ReadonlyMap<string, FileNode>
|
|
103
|
+
strategy: LeafStrategy
|
|
104
|
+
recursive: boolean
|
|
105
|
+
}
|
|
106
|
+
|
|
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> = []
|
|
113
|
+
|
|
114
|
+
for (const child of node.children) {
|
|
115
|
+
if (child.isFile) {
|
|
116
|
+
if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path)
|
|
87
117
|
continue
|
|
88
118
|
}
|
|
89
119
|
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
120
|
+
const childLeaves = walkAllOrNamed(child, params, false, out)
|
|
121
|
+
for (const leaf of childLeaves) subtreeLeaves.push(leaf)
|
|
122
|
+
}
|
|
93
123
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
124
|
+
// Sub-directory barrels are only emitted when the caller asked for them.
|
|
125
|
+
if (!isRoot && !params.recursive) return subtreeLeaves
|
|
126
|
+
|
|
127
|
+
const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))
|
|
128
|
+
|
|
129
|
+
if (exports.length > 0) {
|
|
130
|
+
out.push(makeBarrel(node.path, exports))
|
|
100
131
|
}
|
|
101
132
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return [
|
|
105
|
-
createFile({
|
|
106
|
-
baseName: BARREL_FILENAME,
|
|
107
|
-
path: barrelPath,
|
|
108
|
-
exports,
|
|
109
|
-
sources: [],
|
|
110
|
-
imports: [],
|
|
111
|
-
}),
|
|
112
|
-
]
|
|
133
|
+
return subtreeLeaves
|
|
113
134
|
}
|
|
114
135
|
|
|
115
136
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* consumers can import from any depth.
|
|
119
|
-
*
|
|
120
|
-
* Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.
|
|
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).
|
|
121
139
|
*/
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function collectPropagatedBarrels(node: BuildTree): Array<FileNode> {
|
|
127
|
-
const result: Array<FileNode> = []
|
|
128
|
-
const barrelExports: ReturnType<typeof createExport>[] = []
|
|
140
|
+
function walkPropagate(node: BuildTree, out: Array<FileNode>): void {
|
|
141
|
+
const exports: Array<ExportNode> = []
|
|
129
142
|
|
|
130
143
|
for (const child of node.children) {
|
|
131
144
|
if (child.isFile) {
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} else {
|
|
136
|
-
// Recurse into sub-directory
|
|
137
|
-
const subBarrels = collectPropagatedBarrels(child)
|
|
138
|
-
result.push(...subBarrels)
|
|
139
|
-
|
|
140
|
-
// Export the sub-directory's barrel (not individual files)
|
|
141
|
-
const subBarrelPath = `${child.path}/${BARREL_FILENAME}`
|
|
142
|
-
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
|
|
143
148
|
}
|
|
149
|
+
|
|
150
|
+
walkPropagate(child, out)
|
|
151
|
+
exports.push(createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
createFile({
|
|
149
|
-
baseName: BARREL_FILENAME,
|
|
150
|
-
path: `${node.path}/${BARREL_FILENAME}`,
|
|
151
|
-
exports: barrelExports,
|
|
152
|
-
sources: [],
|
|
153
|
-
imports: [],
|
|
154
|
-
}),
|
|
155
|
-
)
|
|
154
|
+
if (exports.length > 0) {
|
|
155
|
+
out.push(makeBarrel(node.path, exports))
|
|
156
156
|
}
|
|
157
|
+
}
|
|
157
158
|
|
|
158
|
-
|
|
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>
|
|
159
168
|
}
|
|
160
169
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type GetBarrelFilesParams = {
|
|
189
|
+
/**
|
|
190
|
+
* Absolute path to the directory the barrel(s) should be rooted at.
|
|
191
|
+
* Files outside this directory are ignored.
|
|
192
|
+
*/
|
|
193
|
+
outputPath: string
|
|
194
|
+
/**
|
|
195
|
+
* Full set of generated files across all plugins.
|
|
196
|
+
* Used both to discover what to re-export and to read each file's indexable sources.
|
|
197
|
+
*/
|
|
198
|
+
files: ReadonlyArray<FileNode>
|
|
199
|
+
/**
|
|
200
|
+
* Re-export style used in the generated barrel(s).
|
|
201
|
+
*/
|
|
202
|
+
barrelType: BarrelType
|
|
203
|
+
/**
|
|
204
|
+
* When `true`, also generate a barrel for each sub-directory of `outputPath`.
|
|
205
|
+
* Used by per-plugin barrels so that grouped output (e.g. `petController/`) gets its own `index.ts`.
|
|
206
|
+
*
|
|
207
|
+
* Has no effect for `barrelType: 'propagate'`, which always recurses by design.
|
|
208
|
+
*
|
|
209
|
+
* @default false
|
|
210
|
+
*/
|
|
211
|
+
recursive?: boolean
|
|
167
212
|
}
|
|
168
213
|
|
|
169
214
|
/**
|
|
170
|
-
* Generates barrel `FileNode
|
|
171
|
-
* generated source `files`, using the specified `barrelType` strategy.
|
|
215
|
+
* Generates barrel `FileNode`s for the directory rooted at `outputPath`.
|
|
172
216
|
*
|
|
173
|
-
* Files
|
|
174
|
-
*
|
|
175
|
-
* @param outputPath Absolute path to the output directory.
|
|
176
|
-
* @param files All generated files (across all plugins).
|
|
177
|
-
* @param barrelType Barrel generation strategy.
|
|
217
|
+
* Files outside `outputPath`, existing barrel files, and non-source extensions are filtered out
|
|
218
|
+
* before the tree is built.
|
|
178
219
|
*/
|
|
179
|
-
export function getBarrelFiles(outputPath
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (normalizedFilePath.endsWith(`/${BARREL_FILENAME}`)) return false
|
|
186
|
-
const dotIndex = normalizedFilePath.lastIndexOf('.')
|
|
187
|
-
const ext = dotIndex === -1 ? '' : normalizedFilePath.slice(dotIndex)
|
|
188
|
-
return SOURCE_EXTENSIONS.has(ext)
|
|
189
|
-
})
|
|
220
|
+
export function getBarrelFiles({ outputPath, files, barrelType, recursive = false }: GetBarrelFilesParams): Array<FileNode> {
|
|
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> = []
|
|
190
226
|
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
outputPath,
|
|
195
|
-
relevantFiles.map((f) => f.path),
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
switch (barrelType) {
|
|
199
|
-
case 'all':
|
|
200
|
-
return getBarrelFilesAll(tree, relevantFiles)
|
|
201
|
-
case 'named':
|
|
202
|
-
return getBarrelFilesNamed(tree, relevantFiles)
|
|
203
|
-
case 'propagate':
|
|
204
|
-
return getBarrelFilesPropagate(tree)
|
|
205
|
-
default:
|
|
206
|
-
return []
|
|
227
|
+
if (barrelType === 'propagate') {
|
|
228
|
+
walkPropagate(tree, result)
|
|
229
|
+
return result
|
|
207
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
|
|
208
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,67 +0,0 @@
|
|
|
1
|
-
import { posix } from 'node:path'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A node in a directory tree used to compute barrel file exports.
|
|
5
|
-
*
|
|
6
|
-
* Each `TreeNode` represents either a directory or a file entry.
|
|
7
|
-
* Directory nodes have `children`; file nodes have an empty `children` array.
|
|
8
|
-
*/
|
|
9
|
-
export type BuildTree = {
|
|
10
|
-
/**
|
|
11
|
-
* Absolute path of the directory (root of this subtree) or file.
|
|
12
|
-
*/
|
|
13
|
-
path: string
|
|
14
|
-
/**
|
|
15
|
-
* Child nodes (sub-directories and files) within this directory.
|
|
16
|
-
*/
|
|
17
|
-
children: Array<BuildTree>
|
|
18
|
-
/**
|
|
19
|
-
* `true` when this node represents a file (leaf node).
|
|
20
|
-
*/
|
|
21
|
-
isFile: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Builds a `TreeNode` directory tree from a list of absolute file paths.
|
|
26
|
-
*
|
|
27
|
-
* All `filePaths` must be inside `rootPath`. Paths that are outside
|
|
28
|
-
* the root or that equal the root are silently ignored.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```ts
|
|
32
|
-
* const tree = buildTree('/src/gen/types', [
|
|
33
|
-
* '/src/gen/types/pet.ts',
|
|
34
|
-
* '/src/gen/types/user.ts',
|
|
35
|
-
* '/src/gen/types/pets/listPets.ts',
|
|
36
|
-
* ])
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): BuildTree {
|
|
40
|
-
const root: BuildTree = { path: rootPath, children: [], isFile: false }
|
|
41
|
-
|
|
42
|
-
for (const filePath of filePaths) {
|
|
43
|
-
// Only include files inside rootPath
|
|
44
|
-
if (!filePath.startsWith(rootPath + posix.sep) && !filePath.startsWith(rootPath + '/')) {
|
|
45
|
-
continue
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const relative = filePath.slice(rootPath.length).replace(/^\//g, '').replace(/^\\/g, '')
|
|
49
|
-
const parts = relative.split(/[/\\]/).filter(Boolean)
|
|
50
|
-
|
|
51
|
-
let current = root
|
|
52
|
-
for (let i = 0; i < parts.length; i++) {
|
|
53
|
-
const isLast = i === parts.length - 1
|
|
54
|
-
const part = parts[i]!
|
|
55
|
-
const childPath = `${current.path}/${part}`
|
|
56
|
-
|
|
57
|
-
let child = current.children.find((c) => c.path === childPath)
|
|
58
|
-
if (!child) {
|
|
59
|
-
child = { path: childPath, children: [], isFile: isLast }
|
|
60
|
-
current.children.push(child)
|
|
61
|
-
}
|
|
62
|
-
current = child
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return root
|
|
67
|
-
}
|