@kubb/plugin-barrel 5.5.5 → 5.6.0
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 +92 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +46 -60
- package/dist/index.js +86 -109
- package/dist/index.js.map +1 -1
- package/package.json +4 -7
package/dist/index.cjs
CHANGED
|
@@ -23,8 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
//#endregion
|
|
24
24
|
let node_path = require("node:path");
|
|
25
25
|
node_path = __toESM(node_path, 1);
|
|
26
|
-
let
|
|
27
|
-
let _kubb_ast = require("@kubb/ast");
|
|
26
|
+
let _kubb_kit = require("@kubb/kit");
|
|
28
27
|
//#region ../../internals/utils/src/fs.ts
|
|
29
28
|
/**
|
|
30
29
|
* Converts a filesystem path to use POSIX (`/`) separators.
|
|
@@ -104,7 +103,6 @@ function buildTree(rootPath, filePaths) {
|
|
|
104
103
|
const normalized = toPosixPath(filePath);
|
|
105
104
|
if (!normalized.startsWith(rootPrefix)) continue;
|
|
106
105
|
const parts = normalized.slice(rootPrefix.length).split("/");
|
|
107
|
-
if (parts.length === 0) continue;
|
|
108
106
|
let current = root;
|
|
109
107
|
const lastIndex = parts.length - 1;
|
|
110
108
|
for (const [i, part] of parts.entries()) {
|
|
@@ -136,17 +134,17 @@ function sortTree(node) {
|
|
|
136
134
|
function compareByPath(a, b) {
|
|
137
135
|
return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
|
|
138
136
|
}
|
|
139
|
-
function toRelativeModulePath(fromDir, filePath) {
|
|
140
|
-
return `./${
|
|
137
|
+
function toRelativeModulePath({ fromDir, filePath }) {
|
|
138
|
+
return `./${node_path.posix.relative(fromDir, filePath)}`;
|
|
141
139
|
}
|
|
142
140
|
function isBarrelPath(path) {
|
|
143
141
|
return path.endsWith(BARREL_SUFFIX);
|
|
144
142
|
}
|
|
145
|
-
function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
|
|
143
|
+
function makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }) {
|
|
146
144
|
const names = /* @__PURE__ */ new Map();
|
|
147
145
|
const uniqueExports = [];
|
|
148
146
|
for (const item of exports) {
|
|
149
|
-
const itemPath =
|
|
147
|
+
const itemPath = node_path.posix.join(dirPath, item.path);
|
|
150
148
|
const itemNames = Array.isArray(item.name) ? item.name : item.name ? [item.name] : sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name ? [source.name] : []);
|
|
151
149
|
const uniqueNames = [];
|
|
152
150
|
for (const name of itemNames ?? []) {
|
|
@@ -158,94 +156,100 @@ function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
|
|
|
158
156
|
}
|
|
159
157
|
const key = [
|
|
160
158
|
name,
|
|
161
|
-
|
|
159
|
+
node_path.posix.join(dirPath, first),
|
|
162
160
|
itemPath
|
|
163
161
|
].join("\0");
|
|
164
162
|
if (reportedCollisions.has(key)) continue;
|
|
165
163
|
reportedCollisions.add(key);
|
|
166
|
-
|
|
167
|
-
code:
|
|
164
|
+
_kubb_kit.Diagnostics.report({
|
|
165
|
+
code: _kubb_kit.Diagnostics.code.barrelDuplicateExport,
|
|
168
166
|
severity: "error",
|
|
169
167
|
message: `"${name}" is exported by both "${first}" and "${item.path}", so "${dirPath}${BARREL_SUFFIX}" cannot re-export both.`,
|
|
170
168
|
help: "Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names."
|
|
171
169
|
});
|
|
172
170
|
}
|
|
173
|
-
if (!itemNames || item.name == null || uniqueNames.length > 0) uniqueExports.push(item.name == null ? item :
|
|
171
|
+
if (!itemNames || item.name == null || uniqueNames.length > 0) uniqueExports.push(item.name == null ? item : _kubb_kit.ast.factory.createExport({
|
|
174
172
|
...item,
|
|
175
173
|
name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0]
|
|
176
174
|
}));
|
|
177
175
|
}
|
|
178
|
-
return
|
|
176
|
+
return _kubb_kit.ast.factory.createFile({
|
|
179
177
|
baseName: "index.ts",
|
|
180
178
|
path: `${dirPath}${BARREL_SUFFIX}`,
|
|
181
|
-
exports: uniqueExports
|
|
182
|
-
sources: [],
|
|
183
|
-
imports: [],
|
|
184
|
-
banner: void 0,
|
|
185
|
-
footer: void 0
|
|
179
|
+
exports: uniqueExports
|
|
186
180
|
});
|
|
187
181
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
function partitionIndexableNames(sources) {
|
|
194
|
-
const byTypeOnly = /* @__PURE__ */ new Map([[false, /* @__PURE__ */ new Set()], [true, /* @__PURE__ */ new Set()]]);
|
|
195
|
-
for (const source of sources) {
|
|
196
|
-
if (!source.isIndexable || !source.name) continue;
|
|
197
|
-
byTypeOnly.get(Boolean(source.isTypeOnly)).add(source.name);
|
|
198
|
-
}
|
|
199
|
-
return byTypeOnly;
|
|
182
|
+
function indexableNames({ sources, isTypeOnly }) {
|
|
183
|
+
const names = sources.flatMap((source) => source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []);
|
|
184
|
+
return [...new Set(names)].sort();
|
|
200
185
|
}
|
|
201
186
|
const allStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
202
|
-
|
|
203
|
-
|
|
187
|
+
const sources = sourceFile?.sources ?? [];
|
|
188
|
+
if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return [];
|
|
189
|
+
return [_kubb_kit.ast.factory.createExport({ path: toRelativeModulePath({
|
|
190
|
+
fromDir: dirPath,
|
|
191
|
+
filePath: leafPath
|
|
192
|
+
}) })];
|
|
204
193
|
};
|
|
205
194
|
const namedStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
206
|
-
const modulePath = toRelativeModulePath(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
195
|
+
const modulePath = toRelativeModulePath({
|
|
196
|
+
fromDir: dirPath,
|
|
197
|
+
filePath: leafPath
|
|
198
|
+
});
|
|
199
|
+
if (!sourceFile) return [_kubb_kit.ast.factory.createExport({ path: modulePath })];
|
|
200
|
+
const valueNames = indexableNames({
|
|
201
|
+
sources: sourceFile.sources,
|
|
202
|
+
isTypeOnly: false
|
|
203
|
+
});
|
|
204
|
+
const typeNames = indexableNames({
|
|
205
|
+
sources: sourceFile.sources,
|
|
206
|
+
isTypeOnly: true
|
|
207
|
+
});
|
|
208
|
+
if (valueNames.length === 0 && typeNames.length === 0) {
|
|
212
209
|
if (sourceFile.sources.length > 0) return [];
|
|
213
|
-
return [
|
|
210
|
+
return [_kubb_kit.ast.factory.createExport({ path: modulePath })];
|
|
214
211
|
}
|
|
215
212
|
const exports = [];
|
|
216
|
-
if (valueNames.
|
|
217
|
-
name:
|
|
213
|
+
if (valueNames.length > 0) exports.push(_kubb_kit.ast.factory.createExport({
|
|
214
|
+
name: valueNames,
|
|
218
215
|
path: modulePath
|
|
219
216
|
}));
|
|
220
|
-
if (typeNames.
|
|
221
|
-
name:
|
|
217
|
+
if (typeNames.length > 0) exports.push(_kubb_kit.ast.factory.createExport({
|
|
218
|
+
name: typeNames,
|
|
222
219
|
path: modulePath,
|
|
223
220
|
isTypeOnly: true
|
|
224
221
|
}));
|
|
225
222
|
return exports;
|
|
226
223
|
};
|
|
227
|
-
const LEAF_STRATEGIES = /* @__PURE__ */ new Map([["all", allStrategy], ["named", namedStrategy]]);
|
|
228
224
|
/**
|
|
229
225
|
* Post-order walk that yields a barrel per visited directory.
|
|
230
226
|
* Returns the list of leaf file paths collected in this subtree (used by the parent call).
|
|
231
227
|
*/
|
|
232
|
-
function* walkAllOrNamed(node, params, isRoot) {
|
|
228
|
+
function* walkAllOrNamed(node, params, { isRoot, recursive }) {
|
|
233
229
|
const subtreeLeaves = [];
|
|
234
230
|
for (const child of node.children) {
|
|
235
231
|
if (child.isFile) {
|
|
236
232
|
if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path);
|
|
237
233
|
continue;
|
|
238
234
|
}
|
|
239
|
-
const childLeaves = yield* walkAllOrNamed(child, params,
|
|
235
|
+
const childLeaves = yield* walkAllOrNamed(child, params, {
|
|
236
|
+
isRoot: false,
|
|
237
|
+
recursive
|
|
238
|
+
});
|
|
240
239
|
for (const leaf of childLeaves) subtreeLeaves.push(leaf);
|
|
241
240
|
}
|
|
242
|
-
if (!isRoot && !
|
|
241
|
+
if (!isRoot && !recursive) return subtreeLeaves;
|
|
243
242
|
const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({
|
|
244
243
|
dirPath: node.path,
|
|
245
244
|
leafPath,
|
|
246
|
-
sourceFile: params.sourceFiles.get(leafPath)
|
|
245
|
+
sourceFile: params.sourceFiles.get(leafPath)
|
|
247
246
|
}));
|
|
248
|
-
if (exports.length > 0) yield makeBarrel(
|
|
247
|
+
if (exports.length > 0) yield makeBarrel({
|
|
248
|
+
dirPath: node.path,
|
|
249
|
+
exports,
|
|
250
|
+
sourceFiles: params.sourceFiles,
|
|
251
|
+
reportedCollisions: params.reportedCollisions
|
|
252
|
+
});
|
|
249
253
|
return subtreeLeaves;
|
|
250
254
|
}
|
|
251
255
|
/**
|
|
@@ -260,48 +264,46 @@ function* walkNested(node, params) {
|
|
|
260
264
|
for (const child of node.children) {
|
|
261
265
|
if (child.isFile) {
|
|
262
266
|
if (isBarrelPath(child.path)) continue;
|
|
263
|
-
const sourceFile = params.sourceFiles.get(child.path) ?? null;
|
|
264
267
|
exports.push(...params.strategy({
|
|
265
268
|
dirPath: node.path,
|
|
266
269
|
leafPath: child.path,
|
|
267
|
-
sourceFile
|
|
270
|
+
sourceFile: params.sourceFiles.get(child.path)
|
|
268
271
|
}));
|
|
269
272
|
continue;
|
|
270
273
|
}
|
|
271
|
-
if (yield* walkNested(child, params)) exports.push(
|
|
274
|
+
if (yield* walkNested(child, params)) exports.push(_kubb_kit.ast.factory.createExport({ path: toRelativeModulePath({
|
|
275
|
+
fromDir: node.path,
|
|
276
|
+
filePath: `${child.path}${BARREL_SUFFIX}`
|
|
277
|
+
}) }));
|
|
272
278
|
}
|
|
273
279
|
if (exports.length > 0) {
|
|
274
|
-
yield makeBarrel(
|
|
280
|
+
yield makeBarrel({
|
|
281
|
+
dirPath: node.path,
|
|
282
|
+
exports,
|
|
283
|
+
sourceFiles: params.sourceFiles,
|
|
284
|
+
reportedCollisions: params.reportedCollisions
|
|
285
|
+
});
|
|
275
286
|
return true;
|
|
276
287
|
}
|
|
277
288
|
return false;
|
|
278
289
|
}
|
|
279
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
|
|
292
|
+
* files under that path and builds their directory tree. Reuse the result across every barrel
|
|
293
|
+
* derived from the same root rather than re-filtering and re-building per barrel.
|
|
294
|
+
*/
|
|
295
|
+
function buildBarrelIndex(outputPath, files) {
|
|
280
296
|
const outputPrefix = `${toPosixPath(outputPath)}/`;
|
|
281
297
|
const sourceFiles = /* @__PURE__ */ new Map();
|
|
282
|
-
const paths = [];
|
|
283
298
|
for (const file of files) {
|
|
284
299
|
const normalized = toPosixPath(file.path);
|
|
285
300
|
if (!normalized.startsWith(outputPrefix)) continue;
|
|
286
301
|
if (isBarrelPath(normalized)) continue;
|
|
287
302
|
if (!SOURCE_EXTENSIONS.has((0, node_path.extname)(normalized))) continue;
|
|
288
303
|
sourceFiles.set(normalized, file);
|
|
289
|
-
paths.push(normalized);
|
|
290
304
|
}
|
|
291
305
|
return {
|
|
292
|
-
sourceFiles,
|
|
293
|
-
paths
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
|
|
298
|
-
* files under that path and builds their directory tree. Reuse the result across every barrel
|
|
299
|
-
* derived from the same root rather than re-filtering and re-building per barrel.
|
|
300
|
-
*/
|
|
301
|
-
function buildBarrelIndex(outputPath, files) {
|
|
302
|
-
const { sourceFiles, paths } = indexRelevantFiles(files, outputPath);
|
|
303
|
-
return {
|
|
304
|
-
tree: buildTree(outputPath, paths),
|
|
306
|
+
tree: buildTree(outputPath, [...sourceFiles.keys()]),
|
|
305
307
|
sourceFiles
|
|
306
308
|
};
|
|
307
309
|
}
|
|
@@ -315,7 +317,7 @@ function findNode(node, targetPath) {
|
|
|
315
317
|
for (const child of node.children) if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) return findNode(child, targetPath);
|
|
316
318
|
}
|
|
317
319
|
/**
|
|
318
|
-
* Yields barrel `FileNode`s for `targetPath` (or the index root), derived from a shared index.
|
|
320
|
+
* Yields barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.
|
|
319
321
|
* Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per
|
|
320
322
|
* plugin, plus the root) from one index avoids re-scanning the full file set for each.
|
|
321
323
|
*
|
|
@@ -330,8 +332,7 @@ function findNode(node, targetPath) {
|
|
|
330
332
|
function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
|
|
331
333
|
const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree;
|
|
332
334
|
if (!node) return;
|
|
333
|
-
const strategy =
|
|
334
|
-
if (!strategy) return;
|
|
335
|
+
const strategy = barrelType === "named" ? namedStrategy : allStrategy;
|
|
335
336
|
if (nested) {
|
|
336
337
|
yield* walkNested(node, {
|
|
337
338
|
sourceFiles: index.sourceFiles,
|
|
@@ -343,31 +344,11 @@ function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recurs
|
|
|
343
344
|
yield* walkAllOrNamed(node, {
|
|
344
345
|
sourceFiles: index.sourceFiles,
|
|
345
346
|
strategy,
|
|
346
|
-
recursive,
|
|
347
347
|
reportedCollisions
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
* while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.
|
|
353
|
-
*
|
|
354
|
-
* Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
|
|
355
|
-
*/
|
|
356
|
-
function getPluginOutputPrefix(plugin, config) {
|
|
357
|
-
const resolved = toPosixPath((0, node_path.resolve)(config.root, config.output.path, plugin.options.output.path));
|
|
358
|
-
return plugin.options.output.mode === "file" ? resolved : `${resolved}/`;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a
|
|
362
|
-
* trailing `/` matches a directory subtree, and a prefix without one matches that exact file
|
|
363
|
-
* (used for `mode: 'file'` outputs).
|
|
364
|
-
*
|
|
365
|
-
* Both sides are POSIX-normalized so Windows backslash paths match correctly.
|
|
366
|
-
*/
|
|
367
|
-
function isExcludedPath(filePath, prefixes) {
|
|
368
|
-
const normalized = toPosixPath(filePath);
|
|
369
|
-
for (const prefix of prefixes) if (prefix.endsWith("/") ? normalized.startsWith(prefix) : normalized === prefix) return true;
|
|
370
|
-
return false;
|
|
348
|
+
}, {
|
|
349
|
+
isRoot: true,
|
|
350
|
+
recursive
|
|
351
|
+
});
|
|
371
352
|
}
|
|
372
353
|
//#endregion
|
|
373
354
|
//#region src/plugin.ts
|
|
@@ -381,10 +362,9 @@ function isExcludedPath(filePath, prefixes) {
|
|
|
381
362
|
function withBarrelBannerFooter({ file, plugin, config }) {
|
|
382
363
|
const output = plugin.options?.output;
|
|
383
364
|
const resolver = plugin.resolver;
|
|
384
|
-
if (!resolver) return file;
|
|
385
365
|
const hasBanner = output?.banner !== void 0;
|
|
386
366
|
const hasFooter = output?.footer !== void 0;
|
|
387
|
-
if (!hasBanner && !hasFooter) return file;
|
|
367
|
+
if (!resolver || !hasBanner && !hasFooter) return file;
|
|
388
368
|
const context = {
|
|
389
369
|
output,
|
|
390
370
|
config,
|
|
@@ -438,8 +418,8 @@ const pluginBarrelName = "plugin-barrel";
|
|
|
438
418
|
* })
|
|
439
419
|
* ```
|
|
440
420
|
*/
|
|
441
|
-
const pluginBarrel = (0,
|
|
442
|
-
const
|
|
421
|
+
const pluginBarrel = (0, _kubb_kit.definePlugin)(() => {
|
|
422
|
+
const excludedTargets = [];
|
|
443
423
|
const pendingBarrels = [];
|
|
444
424
|
return {
|
|
445
425
|
name: pluginBarrelName,
|
|
@@ -447,24 +427,21 @@ const pluginBarrel = (0, _kubb_core.definePlugin)(() => {
|
|
|
447
427
|
hooks: {
|
|
448
428
|
"kubb:plugin:end"({ plugin, config }) {
|
|
449
429
|
if (plugin.name === "plugin-barrel") return;
|
|
450
|
-
const
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
if (pluginBarrelOpt !== void 0) return pluginBarrelOpt;
|
|
454
|
-
if (configBarrel !== void 0) return configBarrel === false ? false : {
|
|
455
|
-
...configBarrel,
|
|
456
|
-
nested: false
|
|
457
|
-
};
|
|
458
|
-
return false;
|
|
459
|
-
})();
|
|
430
|
+
const barrelConfig = plugin.options.output?.barrel ?? config.output.barrel ?? false;
|
|
431
|
+
const base = node_path.default.resolve(config.root, config.output.path);
|
|
432
|
+
const target = node_path.default.resolve(base, plugin.options.output.path);
|
|
460
433
|
if (barrelConfig === false) {
|
|
461
|
-
|
|
434
|
+
excludedTargets.push(target);
|
|
462
435
|
return;
|
|
463
436
|
}
|
|
464
437
|
if (plugin.options.output.mode === "file") return;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
438
|
+
if (!isPathInside(target, base)) throw new _kubb_kit.Diagnostics.Error({
|
|
439
|
+
code: _kubb_kit.Diagnostics.code.pathTraversal,
|
|
440
|
+
severity: "error",
|
|
441
|
+
message: `Output path "${plugin.options.output.path}" of plugin "${plugin.name}" resolves outside the output directory "${base}".`,
|
|
442
|
+
help: "Keep the plugin `output.path` within `config.output.path`.",
|
|
443
|
+
location: { kind: "config" }
|
|
444
|
+
});
|
|
468
445
|
pendingBarrels.push({
|
|
469
446
|
plugin,
|
|
470
447
|
target,
|
|
@@ -475,8 +452,8 @@ const pluginBarrel = (0, _kubb_core.definePlugin)(() => {
|
|
|
475
452
|
"kubb:plugins:end"({ files, config, upsertFile }) {
|
|
476
453
|
const rootBarrelConfig = config.output.barrel ?? false;
|
|
477
454
|
const outputPath = node_path.default.resolve(config.root, config.output.path);
|
|
478
|
-
const relevantFiles =
|
|
479
|
-
|
|
455
|
+
const relevantFiles = files.filter((file) => !excludedTargets.some((target) => isPathInside(file.path, target)));
|
|
456
|
+
excludedTargets.length = 0;
|
|
480
457
|
const index = buildBarrelIndex(outputPath, relevantFiles);
|
|
481
458
|
const reportedCollisions = /* @__PURE__ */ new Set();
|
|
482
459
|
for (const { plugin, target, barrelType, nested } of pendingBarrels) for (const file of getBarrelFiles({
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["resolve","relative","isAbsolute","resolve","Diagnostics","ast","extname","definePlugin","path"],"sources":["../../../internals/utils/src/fs.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\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 * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\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 the trimmed content plus a newline\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 content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\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 content\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 * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import { extname, resolve } from 'node:path'\nimport { ast, type ExportNode, type FileNode, type SourceNode } from '@kubb/ast'\nimport { Diagnostics } from '@kubb/core'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { 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\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 */\ntype 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\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\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>, sourceFiles: ReadonlyMap<string, FileNode>, reportedCollisions: Set<string>): FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ExportNode> = []\n\n for (const item of exports) {\n const itemPath = toPosixPath(resolve(dirPath, item.path))\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, toPosixPath(resolve(dirPath, first)), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports: uniqueExports,\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 [ast.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 [ast.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 [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(ast.factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(ast.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 reportedCollisions: Set<string>\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, params.sourceFiles, params.reportedCollisions)\n }\n\n return subtreeLeaves\n}\n\ntype NestedWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\n}\n\n/**\n * Recursive walk that yields one barrel per directory, re-exporting files and sub-barrels.\n * Used when nested: true. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: NestedWalkParams): Generator<FileNode, boolean> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n const sourceFile = params.sourceFiles.get(child.path) ?? null\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions)\n return true\n }\n\n return false\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\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: BuildTree\n sourceFiles: ReadonlyMap<string, FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<FileNode>): BarrelIndex {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n return { tree: buildTree(outputPath, paths), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\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 * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields barrel `FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, recursive, reportedCollisions }, 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 // Plain `for...of` over the Set rather than `.values().some()`: the iterator-helper `some`\n // allocates an iterator object per call, and this runs once per file during barrel generation.\n for (const prefix of prefixes) {\n const matched = prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix\n if (matched) return true\n }\n return false\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, 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.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(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 `{ type: 'named' | 'all' }` to opt this plugin into a barrel. Set to `false`\n * (the default) to disable barrel generation for this plugin entirely, which also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default false\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 `{ type: 'named' | 'all' }` to opt into a root barrel. Individual plugins can\n * override this via their own `output.barrel`.\n *\n * @default false\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 hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n}\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`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * 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: './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 const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n const pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\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 false\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 base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n if (!isPathInside(target, base)) {\n throw new Error('Invalid output path')\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["resolve","relative","isAbsolute","posix","Diagnostics","ast","extname","definePlugin","path","Diagnostics"],"sources":["../../../internals/utils/src/fs.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\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 * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\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 the trimmed content plus a newline\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 content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\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 content\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 * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import { extname, posix } from 'node:path'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport { 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\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 */\ntype 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 */\nfunction 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 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\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\nfunction toRelativeModulePath({ fromDir, filePath }: { fromDir: string; filePath: string }): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\ntype MakeBarrelParams = {\n dirPath: string\n exports: Array<ast.ExportNode>\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n reportedCollisions: Set<string>\n}\n\nfunction makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }: MakeBarrelParams): ast.FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ast.ExportNode> = []\n\n for (const item of exports) {\n const itemPath = posix.join(dirPath, item.path)\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n}\n\ntype LeafStrategy = (params: { dirPath: string; leafPath: string; sourceFile: ast.FileNode | undefined }) => Array<ast.ExportNode>\n\nfunction indexableNames({ sources, isTypeOnly }: { sources: ReadonlyArray<ast.SourceNode>; isTypeOnly: boolean }): Array<string> {\n const names = sources.flatMap((source) => (source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []))\n return [...new Set(names)].sort()\n}\n\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const sources = sourceFile?.sources ?? []\n if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return []\n return [ast.factory.createExport({ path: toRelativeModulePath({ fromDir: dirPath, filePath: leafPath }) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath({ fromDir: dirPath, filePath: leafPath })\n\n if (!sourceFile) return [ast.factory.createExport({ path: modulePath })]\n\n const valueNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: false })\n const typeNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: true })\n\n if (valueNames.length === 0 && typeNames.length === 0) {\n if (sourceFile.sources.length > 0) return []\n return [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ast.ExportNode> = []\n if (valueNames.length > 0) {\n exports.push(ast.factory.createExport({ name: valueNames, path: modulePath }))\n }\n if (typeNames.length > 0) {\n exports.push(ast.factory.createExport({ name: typeNames, path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\ntype WalkParams = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\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(\n node: BuildTree,\n params: WalkParams,\n { isRoot, recursive }: { isRoot: boolean; recursive: boolean },\n): Generator<ast.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, { isRoot: false, recursive })\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\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. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: WalkParams): Generator<ast.FileNode, boolean> {\n const exports: Array<ast.ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile: params.sourceFiles.get(child.path) }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath({ fromDir: node.path, filePath: `${child.path}${BARREL_SUFFIX}` }) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\n return true\n }\n\n return false\n}\n\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: BuildTree\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<ast.FileNode>): BarrelIndex {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, ast.FileNode>()\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 }\n\n return { tree: buildTree(outputPath, [...sourceFiles.keys()]), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\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 * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<ast.FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions }, { isRoot: true, recursive })\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport { definePlugin, Diagnostics } from '@kubb/kit'\nimport type { ast, Config, NormalizedPlugin, Plugin } from '@kubb/kit'\nimport type { BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles } 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: ast.FileNode; plugin: NormalizedPlugin; config: Config }): ast.FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!resolver || (!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.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(undefined, context) : file.footer,\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 hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n}\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`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * 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: './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 excludedTargets: Array<string> = []\n const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n // A plugin-level `false` wins over an enabled root barrel.\n const barrelConfig: PluginBarrelConfig | false = plugin.options.output?.barrel ?? config.output.barrel ?? false\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n\n if (barrelConfig === false) {\n excludedTargets.push(target)\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 if (!isPathInside(target, base)) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.pathTraversal,\n severity: 'error',\n message: `Output path \"${plugin.options.output.path}\" of plugin \"${plugin.name}\" resolves outside the output directory \"${base}\".`,\n help: 'Keep the plugin `output.path` within `config.output.path`.',\n location: { kind: 'config' },\n })\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = files.filter((file) => !excludedTargets.some((target) => isPathInside(file.path, target)))\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\n upsertFile(file)\n }\n },\n },\n }\n})\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,31 +1,54 @@
|
|
|
1
1
|
import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
-
import { Plugin } from "@kubb/
|
|
3
|
-
//#region src/
|
|
2
|
+
import { Plugin } from "@kubb/kit";
|
|
3
|
+
//#region src/plugin.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - `'all'` generates `export * from '...'` for every file
|
|
8
|
-
* - `'named'` generates `export { name1, name2 } from '...'` using each file's named exports
|
|
5
|
+
* Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups
|
|
6
|
+
* and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.
|
|
9
7
|
*/
|
|
10
|
-
|
|
8
|
+
export declare const pluginBarrelName = "plugin-barrel";
|
|
11
9
|
/**
|
|
12
|
-
*
|
|
10
|
+
* Generates an `index.ts` for every plugin output directory and one root
|
|
11
|
+
* barrel at `config.output.path/index.ts` after the build completes. Ships
|
|
12
|
+
* with Kubb and is registered by default in `defineConfig`, but generates
|
|
13
|
+
* nothing until a barrel is configured.
|
|
14
|
+
*
|
|
15
|
+
* Each plugin inherits `output.barrel` from `config.output.barrel` (which
|
|
16
|
+
* defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on
|
|
17
|
+
* the root config, a plugin, or both to opt in; a plugin-level `false`
|
|
18
|
+
* overrides an enabled root barrel and also excludes that plugin's files
|
|
19
|
+
* from the root barrel.
|
|
20
|
+
*
|
|
21
|
+
* A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output
|
|
22
|
+
* is a single file. The root barrel re-exports that file directly.
|
|
13
23
|
*
|
|
14
24
|
* @example
|
|
15
25
|
* ```ts
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
26
|
+
* import { defineConfig } from '@kubb/core'
|
|
27
|
+
* import { pluginBarrel } from '@kubb/plugin-barrel'
|
|
28
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
29
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
30
|
+
*
|
|
31
|
+
* export default defineConfig({
|
|
32
|
+
* input: './petStore.yaml',
|
|
33
|
+
* output: { path: 'src/gen', barrel: { type: 'named' } },
|
|
34
|
+
* plugins: [
|
|
35
|
+
* pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
|
|
36
|
+
* pluginZod({ output: { path: 'schemas' } }),
|
|
37
|
+
* pluginBarrel(),
|
|
38
|
+
* ],
|
|
39
|
+
* })
|
|
19
40
|
* ```
|
|
20
41
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
42
|
+
export declare const pluginBarrel: (options?: object | undefined) => Plugin<import("@kubb/core").PluginFactoryOptions<string, object, object, import("@kubb/core").Resolver>>;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/types.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* Barrel export strategy.
|
|
47
|
+
*
|
|
48
|
+
* - `'all'` generates `export * from '...'` for every file
|
|
49
|
+
* - `'named'` generates `export { name1, name2 } from '...'` using each file's named exports
|
|
50
|
+
*/
|
|
51
|
+
type BarrelType = 'all' | 'named';
|
|
29
52
|
/**
|
|
30
53
|
* Barrel configuration at the plugin level.
|
|
31
54
|
* Supports nested barrel generation in subdirectories.
|
|
@@ -51,8 +74,10 @@ type PluginBarrelConfig = {
|
|
|
51
74
|
*/
|
|
52
75
|
nested?: boolean;
|
|
53
76
|
};
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Barrel configuration at the root config level: the export strategy only, since the root barrel is never nested.
|
|
79
|
+
*/
|
|
80
|
+
type BarrelConfig = Pick<PluginBarrelConfig, 'type'>;
|
|
56
81
|
declare global {
|
|
57
82
|
namespace Kubb {
|
|
58
83
|
interface PluginOptionsRegistry {
|
|
@@ -84,45 +109,6 @@ declare global {
|
|
|
84
109
|
}
|
|
85
110
|
}
|
|
86
111
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Canonical plugin name for `@kubb/plugin-barrel`. Used for driver lookups
|
|
89
|
-
* and to guard the `kubb:plugin:end` handler against reacting to its own lifecycle hook.
|
|
90
|
-
*/
|
|
91
|
-
export declare const pluginBarrelName = "plugin-barrel";
|
|
92
|
-
/**
|
|
93
|
-
* Generates an `index.ts` for every plugin output directory and one root
|
|
94
|
-
* barrel at `config.output.path/index.ts` after the build completes. Ships
|
|
95
|
-
* with Kubb and is registered by default in `defineConfig`, but generates
|
|
96
|
-
* nothing until a barrel is configured.
|
|
97
|
-
*
|
|
98
|
-
* Each plugin inherits `output.barrel` from `config.output.barrel` (which
|
|
99
|
-
* defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on
|
|
100
|
-
* the root config, a plugin, or both to opt in; a plugin-level `false`
|
|
101
|
-
* overrides an enabled root barrel and also excludes that plugin's files
|
|
102
|
-
* from the root barrel.
|
|
103
|
-
*
|
|
104
|
-
* A plugin with `output.mode: 'file'` gets no per-plugin barrel, since its output
|
|
105
|
-
* is a single file. The root barrel re-exports that file directly.
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* ```ts
|
|
109
|
-
* import { defineConfig } from '@kubb/core'
|
|
110
|
-
* import { pluginBarrel } from '@kubb/plugin-barrel'
|
|
111
|
-
* import { pluginTs } from '@kubb/plugin-ts'
|
|
112
|
-
* import { pluginZod } from '@kubb/plugin-zod'
|
|
113
|
-
*
|
|
114
|
-
* export default defineConfig({
|
|
115
|
-
* input: './petStore.yaml',
|
|
116
|
-
* output: { path: 'src/gen', barrel: { type: 'named' } },
|
|
117
|
-
* plugins: [
|
|
118
|
-
* pluginTs({ output: { path: 'types', barrel: { type: 'all' } } }),
|
|
119
|
-
* pluginZod({ output: { path: 'schemas' } }),
|
|
120
|
-
* pluginBarrel(),
|
|
121
|
-
* ],
|
|
122
|
-
* })
|
|
123
|
-
* ```
|
|
124
|
-
*/
|
|
125
|
-
export declare const pluginBarrel: (options?: object | undefined) => Plugin<import("@kubb/core").PluginFactoryOptions<string, object, object, import("@kubb/core").Resolver>>;
|
|
126
112
|
//#endregion
|
|
127
113
|
export type { BarrelConfig, BarrelType, PluginBarrelConfig };
|
|
128
114
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
-
import path, { extname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
-
import { Diagnostics, definePlugin } from "@kubb/
|
|
4
|
-
import { ast } from "@kubb/ast";
|
|
2
|
+
import path, { extname, isAbsolute, posix, relative, resolve } from "node:path";
|
|
3
|
+
import { Diagnostics, ast, definePlugin } from "@kubb/kit";
|
|
5
4
|
//#region ../../internals/utils/src/fs.ts
|
|
6
5
|
/**
|
|
7
6
|
* Converts a filesystem path to use POSIX (`/`) separators.
|
|
@@ -81,7 +80,6 @@ function buildTree(rootPath, filePaths) {
|
|
|
81
80
|
const normalized = toPosixPath(filePath);
|
|
82
81
|
if (!normalized.startsWith(rootPrefix)) continue;
|
|
83
82
|
const parts = normalized.slice(rootPrefix.length).split("/");
|
|
84
|
-
if (parts.length === 0) continue;
|
|
85
83
|
let current = root;
|
|
86
84
|
const lastIndex = parts.length - 1;
|
|
87
85
|
for (const [i, part] of parts.entries()) {
|
|
@@ -113,17 +111,17 @@ function sortTree(node) {
|
|
|
113
111
|
function compareByPath(a, b) {
|
|
114
112
|
return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
|
|
115
113
|
}
|
|
116
|
-
function toRelativeModulePath(fromDir, filePath) {
|
|
117
|
-
return `./${
|
|
114
|
+
function toRelativeModulePath({ fromDir, filePath }) {
|
|
115
|
+
return `./${posix.relative(fromDir, filePath)}`;
|
|
118
116
|
}
|
|
119
117
|
function isBarrelPath(path) {
|
|
120
118
|
return path.endsWith(BARREL_SUFFIX);
|
|
121
119
|
}
|
|
122
|
-
function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
|
|
120
|
+
function makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }) {
|
|
123
121
|
const names = /* @__PURE__ */ new Map();
|
|
124
122
|
const uniqueExports = [];
|
|
125
123
|
for (const item of exports) {
|
|
126
|
-
const itemPath =
|
|
124
|
+
const itemPath = posix.join(dirPath, item.path);
|
|
127
125
|
const itemNames = Array.isArray(item.name) ? item.name : item.name ? [item.name] : sourceFiles.get(itemPath)?.sources.flatMap((source) => source.name ? [source.name] : []);
|
|
128
126
|
const uniqueNames = [];
|
|
129
127
|
for (const name of itemNames ?? []) {
|
|
@@ -135,7 +133,7 @@ function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
|
|
|
135
133
|
}
|
|
136
134
|
const key = [
|
|
137
135
|
name,
|
|
138
|
-
|
|
136
|
+
posix.join(dirPath, first),
|
|
139
137
|
itemPath
|
|
140
138
|
].join("\0");
|
|
141
139
|
if (reportedCollisions.has(key)) continue;
|
|
@@ -155,74 +153,80 @@ function makeBarrel(dirPath, exports, sourceFiles, reportedCollisions) {
|
|
|
155
153
|
return ast.factory.createFile({
|
|
156
154
|
baseName: "index.ts",
|
|
157
155
|
path: `${dirPath}${BARREL_SUFFIX}`,
|
|
158
|
-
exports: uniqueExports
|
|
159
|
-
sources: [],
|
|
160
|
-
imports: [],
|
|
161
|
-
banner: void 0,
|
|
162
|
-
footer: void 0
|
|
156
|
+
exports: uniqueExports
|
|
163
157
|
});
|
|
164
158
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
function partitionIndexableNames(sources) {
|
|
171
|
-
const byTypeOnly = /* @__PURE__ */ new Map([[false, /* @__PURE__ */ new Set()], [true, /* @__PURE__ */ new Set()]]);
|
|
172
|
-
for (const source of sources) {
|
|
173
|
-
if (!source.isIndexable || !source.name) continue;
|
|
174
|
-
byTypeOnly.get(Boolean(source.isTypeOnly)).add(source.name);
|
|
175
|
-
}
|
|
176
|
-
return byTypeOnly;
|
|
159
|
+
function indexableNames({ sources, isTypeOnly }) {
|
|
160
|
+
const names = sources.flatMap((source) => source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []);
|
|
161
|
+
return [...new Set(names)].sort();
|
|
177
162
|
}
|
|
178
163
|
const allStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
179
|
-
|
|
180
|
-
|
|
164
|
+
const sources = sourceFile?.sources ?? [];
|
|
165
|
+
if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return [];
|
|
166
|
+
return [ast.factory.createExport({ path: toRelativeModulePath({
|
|
167
|
+
fromDir: dirPath,
|
|
168
|
+
filePath: leafPath
|
|
169
|
+
}) })];
|
|
181
170
|
};
|
|
182
171
|
const namedStrategy = ({ dirPath, leafPath, sourceFile }) => {
|
|
183
|
-
const modulePath = toRelativeModulePath(
|
|
172
|
+
const modulePath = toRelativeModulePath({
|
|
173
|
+
fromDir: dirPath,
|
|
174
|
+
filePath: leafPath
|
|
175
|
+
});
|
|
184
176
|
if (!sourceFile) return [ast.factory.createExport({ path: modulePath })];
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
const valueNames = indexableNames({
|
|
178
|
+
sources: sourceFile.sources,
|
|
179
|
+
isTypeOnly: false
|
|
180
|
+
});
|
|
181
|
+
const typeNames = indexableNames({
|
|
182
|
+
sources: sourceFile.sources,
|
|
183
|
+
isTypeOnly: true
|
|
184
|
+
});
|
|
185
|
+
if (valueNames.length === 0 && typeNames.length === 0) {
|
|
189
186
|
if (sourceFile.sources.length > 0) return [];
|
|
190
187
|
return [ast.factory.createExport({ path: modulePath })];
|
|
191
188
|
}
|
|
192
189
|
const exports = [];
|
|
193
|
-
if (valueNames.
|
|
194
|
-
name:
|
|
190
|
+
if (valueNames.length > 0) exports.push(ast.factory.createExport({
|
|
191
|
+
name: valueNames,
|
|
195
192
|
path: modulePath
|
|
196
193
|
}));
|
|
197
|
-
if (typeNames.
|
|
198
|
-
name:
|
|
194
|
+
if (typeNames.length > 0) exports.push(ast.factory.createExport({
|
|
195
|
+
name: typeNames,
|
|
199
196
|
path: modulePath,
|
|
200
197
|
isTypeOnly: true
|
|
201
198
|
}));
|
|
202
199
|
return exports;
|
|
203
200
|
};
|
|
204
|
-
const LEAF_STRATEGIES = /* @__PURE__ */ new Map([["all", allStrategy], ["named", namedStrategy]]);
|
|
205
201
|
/**
|
|
206
202
|
* Post-order walk that yields a barrel per visited directory.
|
|
207
203
|
* Returns the list of leaf file paths collected in this subtree (used by the parent call).
|
|
208
204
|
*/
|
|
209
|
-
function* walkAllOrNamed(node, params, isRoot) {
|
|
205
|
+
function* walkAllOrNamed(node, params, { isRoot, recursive }) {
|
|
210
206
|
const subtreeLeaves = [];
|
|
211
207
|
for (const child of node.children) {
|
|
212
208
|
if (child.isFile) {
|
|
213
209
|
if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path);
|
|
214
210
|
continue;
|
|
215
211
|
}
|
|
216
|
-
const childLeaves = yield* walkAllOrNamed(child, params,
|
|
212
|
+
const childLeaves = yield* walkAllOrNamed(child, params, {
|
|
213
|
+
isRoot: false,
|
|
214
|
+
recursive
|
|
215
|
+
});
|
|
217
216
|
for (const leaf of childLeaves) subtreeLeaves.push(leaf);
|
|
218
217
|
}
|
|
219
|
-
if (!isRoot && !
|
|
218
|
+
if (!isRoot && !recursive) return subtreeLeaves;
|
|
220
219
|
const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({
|
|
221
220
|
dirPath: node.path,
|
|
222
221
|
leafPath,
|
|
223
|
-
sourceFile: params.sourceFiles.get(leafPath)
|
|
222
|
+
sourceFile: params.sourceFiles.get(leafPath)
|
|
224
223
|
}));
|
|
225
|
-
if (exports.length > 0) yield makeBarrel(
|
|
224
|
+
if (exports.length > 0) yield makeBarrel({
|
|
225
|
+
dirPath: node.path,
|
|
226
|
+
exports,
|
|
227
|
+
sourceFiles: params.sourceFiles,
|
|
228
|
+
reportedCollisions: params.reportedCollisions
|
|
229
|
+
});
|
|
226
230
|
return subtreeLeaves;
|
|
227
231
|
}
|
|
228
232
|
/**
|
|
@@ -237,48 +241,46 @@ function* walkNested(node, params) {
|
|
|
237
241
|
for (const child of node.children) {
|
|
238
242
|
if (child.isFile) {
|
|
239
243
|
if (isBarrelPath(child.path)) continue;
|
|
240
|
-
const sourceFile = params.sourceFiles.get(child.path) ?? null;
|
|
241
244
|
exports.push(...params.strategy({
|
|
242
245
|
dirPath: node.path,
|
|
243
246
|
leafPath: child.path,
|
|
244
|
-
sourceFile
|
|
247
|
+
sourceFile: params.sourceFiles.get(child.path)
|
|
245
248
|
}));
|
|
246
249
|
continue;
|
|
247
250
|
}
|
|
248
|
-
if (yield* walkNested(child, params)) exports.push(ast.factory.createExport({ path: toRelativeModulePath(
|
|
251
|
+
if (yield* walkNested(child, params)) exports.push(ast.factory.createExport({ path: toRelativeModulePath({
|
|
252
|
+
fromDir: node.path,
|
|
253
|
+
filePath: `${child.path}${BARREL_SUFFIX}`
|
|
254
|
+
}) }));
|
|
249
255
|
}
|
|
250
256
|
if (exports.length > 0) {
|
|
251
|
-
yield makeBarrel(
|
|
257
|
+
yield makeBarrel({
|
|
258
|
+
dirPath: node.path,
|
|
259
|
+
exports,
|
|
260
|
+
sourceFiles: params.sourceFiles,
|
|
261
|
+
reportedCollisions: params.reportedCollisions
|
|
262
|
+
});
|
|
252
263
|
return true;
|
|
253
264
|
}
|
|
254
265
|
return false;
|
|
255
266
|
}
|
|
256
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
|
|
269
|
+
* files under that path and builds their directory tree. Reuse the result across every barrel
|
|
270
|
+
* derived from the same root rather than re-filtering and re-building per barrel.
|
|
271
|
+
*/
|
|
272
|
+
function buildBarrelIndex(outputPath, files) {
|
|
257
273
|
const outputPrefix = `${toPosixPath(outputPath)}/`;
|
|
258
274
|
const sourceFiles = /* @__PURE__ */ new Map();
|
|
259
|
-
const paths = [];
|
|
260
275
|
for (const file of files) {
|
|
261
276
|
const normalized = toPosixPath(file.path);
|
|
262
277
|
if (!normalized.startsWith(outputPrefix)) continue;
|
|
263
278
|
if (isBarrelPath(normalized)) continue;
|
|
264
279
|
if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue;
|
|
265
280
|
sourceFiles.set(normalized, file);
|
|
266
|
-
paths.push(normalized);
|
|
267
281
|
}
|
|
268
282
|
return {
|
|
269
|
-
sourceFiles,
|
|
270
|
-
paths
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source
|
|
275
|
-
* files under that path and builds their directory tree. Reuse the result across every barrel
|
|
276
|
-
* derived from the same root rather than re-filtering and re-building per barrel.
|
|
277
|
-
*/
|
|
278
|
-
function buildBarrelIndex(outputPath, files) {
|
|
279
|
-
const { sourceFiles, paths } = indexRelevantFiles(files, outputPath);
|
|
280
|
-
return {
|
|
281
|
-
tree: buildTree(outputPath, paths),
|
|
283
|
+
tree: buildTree(outputPath, [...sourceFiles.keys()]),
|
|
282
284
|
sourceFiles
|
|
283
285
|
};
|
|
284
286
|
}
|
|
@@ -292,7 +294,7 @@ function findNode(node, targetPath) {
|
|
|
292
294
|
for (const child of node.children) if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) return findNode(child, targetPath);
|
|
293
295
|
}
|
|
294
296
|
/**
|
|
295
|
-
* Yields barrel `FileNode`s for `targetPath` (or the index root), derived from a shared index.
|
|
297
|
+
* Yields barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.
|
|
296
298
|
* Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per
|
|
297
299
|
* plugin, plus the root) from one index avoids re-scanning the full file set for each.
|
|
298
300
|
*
|
|
@@ -307,8 +309,7 @@ function findNode(node, targetPath) {
|
|
|
307
309
|
function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recursive = false, reportedCollisions = /* @__PURE__ */ new Set() }) {
|
|
308
310
|
const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree;
|
|
309
311
|
if (!node) return;
|
|
310
|
-
const strategy =
|
|
311
|
-
if (!strategy) return;
|
|
312
|
+
const strategy = barrelType === "named" ? namedStrategy : allStrategy;
|
|
312
313
|
if (nested) {
|
|
313
314
|
yield* walkNested(node, {
|
|
314
315
|
sourceFiles: index.sourceFiles,
|
|
@@ -320,31 +321,11 @@ function* getBarrelFiles({ index, targetPath, barrelType, nested = false, recurs
|
|
|
320
321
|
yield* walkAllOrNamed(node, {
|
|
321
322
|
sourceFiles: index.sourceFiles,
|
|
322
323
|
strategy,
|
|
323
|
-
recursive,
|
|
324
324
|
reportedCollisions
|
|
325
|
-
},
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
* while a `mode: 'file'` output (the path is the file itself) gets the exact path with no trailing `/`.
|
|
330
|
-
*
|
|
331
|
-
* Used to detect (and later exclude) files generated by plugins that opted out of the root barrel.
|
|
332
|
-
*/
|
|
333
|
-
function getPluginOutputPrefix(plugin, config) {
|
|
334
|
-
const resolved = toPosixPath(resolve(config.root, config.output.path, plugin.options.output.path));
|
|
335
|
-
return plugin.options.output.mode === "file" ? resolved : `${resolved}/`;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Returns `true` when `filePath` lives under any of the given excluded prefixes. A prefix with a
|
|
339
|
-
* trailing `/` matches a directory subtree, and a prefix without one matches that exact file
|
|
340
|
-
* (used for `mode: 'file'` outputs).
|
|
341
|
-
*
|
|
342
|
-
* Both sides are POSIX-normalized so Windows backslash paths match correctly.
|
|
343
|
-
*/
|
|
344
|
-
function isExcludedPath(filePath, prefixes) {
|
|
345
|
-
const normalized = toPosixPath(filePath);
|
|
346
|
-
for (const prefix of prefixes) if (prefix.endsWith("/") ? normalized.startsWith(prefix) : normalized === prefix) return true;
|
|
347
|
-
return false;
|
|
325
|
+
}, {
|
|
326
|
+
isRoot: true,
|
|
327
|
+
recursive
|
|
328
|
+
});
|
|
348
329
|
}
|
|
349
330
|
//#endregion
|
|
350
331
|
//#region src/plugin.ts
|
|
@@ -358,10 +339,9 @@ function isExcludedPath(filePath, prefixes) {
|
|
|
358
339
|
function withBarrelBannerFooter({ file, plugin, config }) {
|
|
359
340
|
const output = plugin.options?.output;
|
|
360
341
|
const resolver = plugin.resolver;
|
|
361
|
-
if (!resolver) return file;
|
|
362
342
|
const hasBanner = output?.banner !== void 0;
|
|
363
343
|
const hasFooter = output?.footer !== void 0;
|
|
364
|
-
if (!hasBanner && !hasFooter) return file;
|
|
344
|
+
if (!resolver || !hasBanner && !hasFooter) return file;
|
|
365
345
|
const context = {
|
|
366
346
|
output,
|
|
367
347
|
config,
|
|
@@ -416,7 +396,7 @@ const pluginBarrelName = "plugin-barrel";
|
|
|
416
396
|
* ```
|
|
417
397
|
*/
|
|
418
398
|
const pluginBarrel = definePlugin(() => {
|
|
419
|
-
const
|
|
399
|
+
const excludedTargets = [];
|
|
420
400
|
const pendingBarrels = [];
|
|
421
401
|
return {
|
|
422
402
|
name: pluginBarrelName,
|
|
@@ -424,24 +404,21 @@ const pluginBarrel = definePlugin(() => {
|
|
|
424
404
|
hooks: {
|
|
425
405
|
"kubb:plugin:end"({ plugin, config }) {
|
|
426
406
|
if (plugin.name === "plugin-barrel") return;
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
if (pluginBarrelOpt !== void 0) return pluginBarrelOpt;
|
|
431
|
-
if (configBarrel !== void 0) return configBarrel === false ? false : {
|
|
432
|
-
...configBarrel,
|
|
433
|
-
nested: false
|
|
434
|
-
};
|
|
435
|
-
return false;
|
|
436
|
-
})();
|
|
407
|
+
const barrelConfig = plugin.options.output?.barrel ?? config.output.barrel ?? false;
|
|
408
|
+
const base = path.resolve(config.root, config.output.path);
|
|
409
|
+
const target = path.resolve(base, plugin.options.output.path);
|
|
437
410
|
if (barrelConfig === false) {
|
|
438
|
-
|
|
411
|
+
excludedTargets.push(target);
|
|
439
412
|
return;
|
|
440
413
|
}
|
|
441
414
|
if (plugin.options.output.mode === "file") return;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
415
|
+
if (!isPathInside(target, base)) throw new Diagnostics.Error({
|
|
416
|
+
code: Diagnostics.code.pathTraversal,
|
|
417
|
+
severity: "error",
|
|
418
|
+
message: `Output path "${plugin.options.output.path}" of plugin "${plugin.name}" resolves outside the output directory "${base}".`,
|
|
419
|
+
help: "Keep the plugin `output.path` within `config.output.path`.",
|
|
420
|
+
location: { kind: "config" }
|
|
421
|
+
});
|
|
445
422
|
pendingBarrels.push({
|
|
446
423
|
plugin,
|
|
447
424
|
target,
|
|
@@ -452,8 +429,8 @@ const pluginBarrel = definePlugin(() => {
|
|
|
452
429
|
"kubb:plugins:end"({ files, config, upsertFile }) {
|
|
453
430
|
const rootBarrelConfig = config.output.barrel ?? false;
|
|
454
431
|
const outputPath = path.resolve(config.root, config.output.path);
|
|
455
|
-
const relevantFiles =
|
|
456
|
-
|
|
432
|
+
const relevantFiles = files.filter((file) => !excludedTargets.some((target) => isPathInside(file.path, target)));
|
|
433
|
+
excludedTargets.length = 0;
|
|
457
434
|
const index = buildBarrelIndex(outputPath, relevantFiles);
|
|
458
435
|
const reportedCollisions = /* @__PURE__ */ new Set();
|
|
459
436
|
for (const { plugin, target, barrelType, nested } of pendingBarrels) for (const file of getBarrelFiles({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../internals/utils/src/fs.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\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 * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\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 the trimmed content plus a newline\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 content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\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 content\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 * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import { extname, resolve } from 'node:path'\nimport { ast, type ExportNode, type FileNode, type SourceNode } from '@kubb/ast'\nimport { Diagnostics } from '@kubb/core'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport { 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\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 */\ntype 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\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\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>, sourceFiles: ReadonlyMap<string, FileNode>, reportedCollisions: Set<string>): FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ExportNode> = []\n\n for (const item of exports) {\n const itemPath = toPosixPath(resolve(dirPath, item.path))\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, toPosixPath(resolve(dirPath, first)), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({\n baseName: 'index.ts',\n path: `${dirPath}${BARREL_SUFFIX}`,\n exports: uniqueExports,\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 [ast.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 [ast.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 [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ExportNode> = []\n if (valueNames.size > 0) {\n exports.push(ast.factory.createExport({ name: [...valueNames].sort(), path: modulePath }))\n }\n if (typeNames.size > 0) {\n exports.push(ast.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 reportedCollisions: Set<string>\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, params.sourceFiles, params.reportedCollisions)\n }\n\n return subtreeLeaves\n}\n\ntype NestedWalkParams = {\n sourceFiles: ReadonlyMap<string, FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\n}\n\n/**\n * Recursive walk that yields one barrel per directory, re-exporting files and sub-barrels.\n * Used when nested: true. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: NestedWalkParams): Generator<FileNode, boolean> {\n const exports: Array<ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n const sourceFile = params.sourceFiles.get(child.path) ?? null\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel(node.path, exports, params.sourceFiles, params.reportedCollisions)\n return true\n }\n\n return false\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\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: BuildTree\n sourceFiles: ReadonlyMap<string, FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<FileNode>): BarrelIndex {\n const { sourceFiles, paths } = indexRelevantFiles(files, outputPath)\n return { tree: buildTree(outputPath, paths), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\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 * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields barrel `FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = LEAF_STRATEGIES.get(barrelType)\n if (!strategy) return\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, recursive, reportedCollisions }, 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 // Plain `for...of` over the Set rather than `.values().some()`: the iterator-helper `some`\n // allocates an iterator object per call, and this runs once per file during barrel generation.\n for (const prefix of prefixes) {\n const matched = prefix.endsWith('/') ? normalized.startsWith(prefix) : normalized === prefix\n if (matched) return true\n }\n return false\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { definePlugin } from '@kubb/core'\nimport type { Config, NormalizedPlugin, Plugin } from '@kubb/core'\nimport type { BarrelConfig, BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, 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.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(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 `{ type: 'named' | 'all' }` to opt this plugin into a barrel. Set to `false`\n * (the default) to disable barrel generation for this plugin entirely, which also\n * excludes the plugin's files from the root barrel.\n *\n * Falls back to `config.output.barrel` when omitted.\n *\n * @default false\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 `{ type: 'named' | 'all' }` to opt into a root barrel. Individual plugins can\n * override this via their own `output.barrel`.\n *\n * @default false\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 hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n}\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`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * 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: './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 const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n const pluginBarrelOpt = plugin.options.output?.barrel\n const configBarrel = config.output.barrel\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 false\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 base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n if (!isPathInside(target, base)) {\n throw new Error('Invalid output path')\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes))\n excludedPrefixes.clear()\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\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","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\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 * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\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 * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\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 the trimmed content plus a newline\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 content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\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 content\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 * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import { extname, posix } from 'node:path'\nimport { ast, Diagnostics } from '@kubb/kit'\nimport { 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\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 */\ntype 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 */\nfunction 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 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\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\nfunction toRelativeModulePath({ fromDir, filePath }: { fromDir: string; filePath: string }): string {\n return `./${posix.relative(fromDir, filePath)}`\n}\n\nfunction isBarrelPath(path: string): boolean {\n return path.endsWith(BARREL_SUFFIX)\n}\n\ntype MakeBarrelParams = {\n dirPath: string\n exports: Array<ast.ExportNode>\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n reportedCollisions: Set<string>\n}\n\nfunction makeBarrel({ dirPath, exports, sourceFiles, reportedCollisions }: MakeBarrelParams): ast.FileNode {\n const names = new Map<string, string>()\n const uniqueExports: Array<ast.ExportNode> = []\n\n for (const item of exports) {\n const itemPath = posix.join(dirPath, item.path)\n const itemNames = Array.isArray(item.name)\n ? item.name\n : item.name\n ? [item.name]\n : sourceFiles.get(itemPath)?.sources.flatMap((source) => (source.name ? [source.name] : []))\n const uniqueNames: Array<string> = []\n\n for (const name of itemNames ?? []) {\n const first = names.get(name)\n if (!first) {\n names.set(name, item.path)\n uniqueNames.push(name)\n continue\n }\n\n const key = [name, posix.join(dirPath, first), itemPath].join('\\0')\n if (reportedCollisions.has(key)) continue\n reportedCollisions.add(key)\n Diagnostics.report({\n code: Diagnostics.code.barrelDuplicateExport,\n severity: 'error',\n message: `\"${name}\" is exported by both \"${first}\" and \"${item.path}\", so \"${dirPath}${BARREL_SUFFIX}\" cannot re-export both.`,\n help: 'Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names.',\n })\n }\n\n if (!itemNames || item.name == null || uniqueNames.length > 0) {\n uniqueExports.push(item.name == null ? item : ast.factory.createExport({ ...item, name: Array.isArray(item.name) ? uniqueNames : uniqueNames[0] }))\n }\n }\n\n return ast.factory.createFile({ baseName: 'index.ts', path: `${dirPath}${BARREL_SUFFIX}`, exports: uniqueExports })\n}\n\ntype LeafStrategy = (params: { dirPath: string; leafPath: string; sourceFile: ast.FileNode | undefined }) => Array<ast.ExportNode>\n\nfunction indexableNames({ sources, isTypeOnly }: { sources: ReadonlyArray<ast.SourceNode>; isTypeOnly: boolean }): Array<string> {\n const names = sources.flatMap((source) => (source.isIndexable && source.name && Boolean(source.isTypeOnly) === isTypeOnly ? [source.name] : []))\n return [...new Set(names)].sort()\n}\n\nconst allStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const sources = sourceFile?.sources ?? []\n if (sources.length > 0 && !sources.some((source) => source.isIndexable)) return []\n return [ast.factory.createExport({ path: toRelativeModulePath({ fromDir: dirPath, filePath: leafPath }) })]\n}\n\nconst namedStrategy: LeafStrategy = ({ dirPath, leafPath, sourceFile }) => {\n const modulePath = toRelativeModulePath({ fromDir: dirPath, filePath: leafPath })\n\n if (!sourceFile) return [ast.factory.createExport({ path: modulePath })]\n\n const valueNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: false })\n const typeNames = indexableNames({ sources: sourceFile.sources, isTypeOnly: true })\n\n if (valueNames.length === 0 && typeNames.length === 0) {\n if (sourceFile.sources.length > 0) return []\n return [ast.factory.createExport({ path: modulePath })]\n }\n\n const exports: Array<ast.ExportNode> = []\n if (valueNames.length > 0) {\n exports.push(ast.factory.createExport({ name: valueNames, path: modulePath }))\n }\n if (typeNames.length > 0) {\n exports.push(ast.factory.createExport({ name: typeNames, path: modulePath, isTypeOnly: true }))\n }\n return exports\n}\n\ntype WalkParams = {\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n strategy: LeafStrategy\n reportedCollisions: Set<string>\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(\n node: BuildTree,\n params: WalkParams,\n { isRoot, recursive }: { isRoot: boolean; recursive: boolean },\n): Generator<ast.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, { isRoot: false, recursive })\n for (const leaf of childLeaves) subtreeLeaves.push(leaf)\n }\n\n if (!isRoot && !recursive) return subtreeLeaves\n\n const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({ dirPath: node.path, leafPath, sourceFile: params.sourceFiles.get(leafPath) }))\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\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. Leaf files honor the barrel `strategy`, so `named` emits explicit\n * named exports instead of wildcards. Sub-directory barrels are chained with a wildcard\n * re-export, which forwards the names the child barrel already curated. Returns whether this\n * node yielded a barrel, so a parent never re-exports a sub-directory that produced nothing.\n */\nfunction* walkNested(node: BuildTree, params: WalkParams): Generator<ast.FileNode, boolean> {\n const exports: Array<ast.ExportNode> = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (isBarrelPath(child.path)) continue\n exports.push(...params.strategy({ dirPath: node.path, leafPath: child.path, sourceFile: params.sourceFiles.get(child.path) }))\n continue\n }\n\n const childYieldedBarrel = yield* walkNested(child, params)\n if (childYieldedBarrel) {\n exports.push(ast.factory.createExport({ path: toRelativeModulePath({ fromDir: node.path, filePath: `${child.path}${BARREL_SUFFIX}` }) }))\n }\n }\n\n if (exports.length > 0) {\n yield makeBarrel({ dirPath: node.path, exports, sourceFiles: params.sourceFiles, reportedCollisions: params.reportedCollisions })\n return true\n }\n\n return false\n}\n\n/**\n * A directory tree plus the source-file lookup it was built from, scoped to a single output root.\n * Build it once with {@link buildBarrelIndex} and derive every barrel (per-plugin and root) from\n * it via {@link getBarrelFiles}, instead of re-scanning the full file set once per barrel.\n */\ntype BarrelIndex = {\n tree: BuildTree\n sourceFiles: ReadonlyMap<string, ast.FileNode>\n}\n\n/**\n * Indexes `files` once for the directory rooted at `outputPath`: filters to indexable source\n * files under that path and builds their directory tree. Reuse the result across every barrel\n * derived from the same root rather than re-filtering and re-building per barrel.\n */\nexport function buildBarrelIndex(outputPath: string, files: ReadonlyArray<ast.FileNode>): BarrelIndex {\n const outputPrefix = `${toPosixPath(outputPath)}/`\n const sourceFiles = new Map<string, ast.FileNode>()\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 }\n\n return { tree: buildTree(outputPath, [...sourceFiles.keys()]), sourceFiles }\n}\n\n/**\n * Locates the node for `targetPath` within an index tree, walking down through directory nodes\n * only. Returns `undefined` when no file exists at or under `targetPath` (nothing to barrel).\n */\nfunction findNode(node: BuildTree, targetPath: string): BuildTree | undefined {\n if (node.path === targetPath) return node\n if (node.isFile || !targetPath.startsWith(`${node.path}/`)) return undefined\n\n for (const child of node.children) {\n if (!child.isFile && (child.path === targetPath || targetPath.startsWith(`${child.path}/`))) {\n return findNode(child, targetPath)\n }\n }\n\n return undefined\n}\n\ntype GetBarrelFilesParams = {\n /**\n * Index built once via {@link buildBarrelIndex} for the shared output root.\n */\n index: BarrelIndex\n /**\n * Absolute directory the barrel(s) should be rooted at, a subtree of the index root.\n * Defaults to the index root.\n */\n targetPath?: string\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 * Collision identities reported by related barrel generations in the same build.\n */\n reportedCollisions?: Set<string>\n}\n\n/**\n * Yields barrel `ast.FileNode`s for `targetPath` (or the index root), derived from a shared index.\n * Locating the subtree is a bounded walk down from the root, so deriving many barrels (one per\n * plugin, plus the root) from one index avoids re-scanning the full file set for each.\n *\n * @example\n * ```ts\n * const index = buildBarrelIndex(outputPath, files)\n * for (const file of getBarrelFiles({ index, targetPath, barrelType })) {\n * upsertFile(file)\n * }\n * ```\n */\nexport function* getBarrelFiles({\n index,\n targetPath,\n barrelType,\n nested = false,\n recursive = false,\n reportedCollisions = new Set(),\n}: GetBarrelFilesParams): Generator<ast.FileNode> {\n const node = targetPath ? findNode(index.tree, toPosixPath(targetPath)) : index.tree\n if (!node) return\n\n const strategy = barrelType === 'named' ? namedStrategy : allStrategy\n\n if (nested) {\n yield* walkNested(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions })\n return\n }\n\n yield* walkAllOrNamed(node, { sourceFiles: index.sourceFiles, strategy, reportedCollisions }, { isRoot: true, recursive })\n}\n","import path from 'node:path'\nimport { isPathInside } from '@internals/utils'\nimport { definePlugin, Diagnostics } from '@kubb/kit'\nimport type { ast, Config, NormalizedPlugin, Plugin } from '@kubb/kit'\nimport type { BarrelType, PluginBarrelConfig } from './types.ts'\nimport { buildBarrelIndex, getBarrelFiles } 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: ast.FileNode; plugin: NormalizedPlugin; config: Config }): ast.FileNode {\n const output = plugin.options?.output\n const resolver = plugin.resolver\n const hasBanner = output?.banner !== undefined\n const hasFooter = output?.footer !== undefined\n if (!resolver || (!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.default.banner(undefined, context) : file.banner,\n footer: hasFooter ? resolver.default.footer(undefined, context) : file.footer,\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 hook.\n */\nexport const pluginBarrelName = 'plugin-barrel' satisfies Plugin['name']\n\ntype PendingBarrel = {\n plugin: NormalizedPlugin\n target: string\n barrelType: BarrelType\n nested: boolean\n}\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`, but generates\n * nothing until a barrel is configured.\n *\n * Each plugin inherits `output.barrel` from `config.output.barrel` (which\n * defaults to `false`, no barrel). Set `barrel: { type: 'named' | 'all' }` on\n * the root config, a plugin, or both to opt in; a plugin-level `false`\n * overrides an enabled root barrel and also excludes that plugin's files\n * 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: './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 excludedTargets: Array<string> = []\n const pendingBarrels: Array<PendingBarrel> = []\n\n return {\n name: pluginBarrelName,\n enforce: 'post' as const,\n hooks: {\n 'kubb:plugin:end'({ plugin, config }) {\n // Skip reactions to the barrel plugin's own lifecycle hook\n if (plugin.name === pluginBarrelName) return\n\n // A plugin-level `false` wins over an enabled root barrel.\n const barrelConfig: PluginBarrelConfig | false = plugin.options.output?.barrel ?? config.output.barrel ?? false\n const base = path.resolve(config.root, config.output.path)\n const target = path.resolve(base, plugin.options.output.path)\n\n if (barrelConfig === false) {\n excludedTargets.push(target)\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 if (!isPathInside(target, base)) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.pathTraversal,\n severity: 'error',\n message: `Output path \"${plugin.options.output.path}\" of plugin \"${plugin.name}\" resolves outside the output directory \"${base}\".`,\n help: 'Keep the plugin `output.path` within `config.output.path`.',\n location: { kind: 'config' },\n })\n }\n\n // Only the target directory and barrel strategy are recorded here. The actual file-set\n // scan and directory-tree build happen once, in `kubb:plugins:end`, and are shared by\n // every plugin barrel plus the root barrel instead of repeating per plugin.\n pendingBarrels.push({ plugin, target, barrelType: barrelConfig.type, nested: barrelConfig.nested ?? false })\n },\n 'kubb:plugins:end'({ files, config, upsertFile }) {\n const rootBarrelConfig = config.output.barrel ?? false\n const outputPath = path.resolve(config.root, config.output.path)\n\n // A `barrel: false` plugin gets no barrel and stays out of the root, so drop its files\n // once here. Every barrel below then derives from a single index of what remains.\n const relevantFiles = files.filter((file) => !excludedTargets.some((target) => isPathInside(file.path, target)))\n excludedTargets.length = 0\n\n const index = buildBarrelIndex(outputPath, relevantFiles)\n const reportedCollisions = new Set<string>()\n\n for (const { plugin, target, barrelType, nested } of pendingBarrels) {\n for (const file of getBarrelFiles({ index, targetPath: target, barrelType, nested, recursive: true, reportedCollisions })) {\n upsertFile(withBarrelBannerFooter({ file, plugin, config }))\n }\n }\n pendingBarrels.length = 0\n\n if (rootBarrelConfig === false) return\n\n for (const file of getBarrelFiles({ index, barrelType: rootBarrelConfig.type, reportedCollisions })) {\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.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"description": "Barrel-file plugin for Kubb. Automatically generates index.ts re-export files per plugin output directory.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"barrel",
|
|
@@ -58,14 +58,11 @@
|
|
|
58
58
|
"registry": "https://registry.npmjs.org/"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@kubb/
|
|
62
|
-
"@kubb/core": "5.5.5"
|
|
61
|
+
"@kubb/kit": "5.6.0"
|
|
63
62
|
},
|
|
64
63
|
"devDependencies": {
|
|
65
|
-
"@internals/utils": "0.0.1"
|
|
66
|
-
|
|
67
|
-
"peerDependencies": {
|
|
68
|
-
"@kubb/core": "5.5.5"
|
|
64
|
+
"@internals/utils": "0.0.1",
|
|
65
|
+
"@kubb/core": "5.6.0"
|
|
69
66
|
},
|
|
70
67
|
"engines": {
|
|
71
68
|
"node": ">=22"
|