@kubb/core 5.0.0-alpha.3 → 5.0.0-alpha.30
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/PluginDriver-D110FoJ-.d.ts +1632 -0
- package/dist/hooks.cjs +12 -27
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +11 -36
- package/dist/hooks.js +13 -27
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +1410 -823
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +597 -95
- package/dist/index.js +1391 -818
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/Kubb.ts +40 -58
- package/src/{PluginManager.ts → PluginDriver.ts} +165 -177
- package/src/build.ts +167 -44
- package/src/config.ts +9 -8
- package/src/constants.ts +40 -7
- package/src/createAdapter.ts +25 -0
- package/src/createPlugin.ts +30 -0
- package/src/createStorage.ts +58 -0
- package/src/defineGenerator.ts +126 -0
- package/src/defineLogger.ts +13 -3
- package/src/definePresets.ts +16 -0
- package/src/defineResolver.ts +457 -0
- package/src/hooks/index.ts +1 -6
- package/src/hooks/useDriver.ts +11 -0
- package/src/hooks/useMode.ts +5 -5
- package/src/hooks/usePlugin.ts +3 -3
- package/src/index.ts +18 -7
- package/src/renderNode.tsx +25 -0
- package/src/storages/fsStorage.ts +2 -2
- package/src/storages/memoryStorage.ts +2 -2
- package/src/types.ts +589 -52
- package/src/utils/FunctionParams.ts +2 -2
- package/src/utils/TreeNode.ts +45 -7
- package/src/utils/diagnostics.ts +4 -1
- package/src/utils/executeStrategies.ts +29 -10
- package/src/utils/formatters.ts +10 -21
- package/src/utils/getBarrelFiles.ts +83 -10
- package/src/utils/getConfigs.ts +8 -22
- package/src/utils/getPreset.ts +78 -0
- package/src/utils/linters.ts +23 -3
- package/src/utils/packageJSON.ts +76 -0
- package/dist/types-CiPWLv-5.d.ts +0 -1001
- package/src/BarrelManager.ts +0 -74
- package/src/PackageManager.ts +0 -180
- package/src/PromiseManager.ts +0 -40
- package/src/defineAdapter.ts +0 -22
- package/src/definePlugin.ts +0 -12
- package/src/defineStorage.ts +0 -56
- package/src/errors.ts +0 -1
- package/src/hooks/useKubb.ts +0 -22
- package/src/hooks/usePluginManager.ts +0 -11
- package/src/utils/getPlugins.ts +0 -23
package/dist/index.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
|
-
import mod from "node:module";
|
|
3
2
|
import { EventEmitter } from "node:events";
|
|
4
|
-
import { parseArgs, styleText } from "node:util";
|
|
5
3
|
import { readFileSync } from "node:fs";
|
|
6
4
|
import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
7
5
|
import path, { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
|
|
8
|
-
import { definePrinter } from "@kubb/ast";
|
|
9
|
-
import { createFabric } from "@kubb/react-fabric";
|
|
6
|
+
import { composeTransformers, composeTransformers as composeTransformers$1, definePrinter, isOperationNode, isSchemaNode, transform, walk } from "@kubb/ast";
|
|
7
|
+
import { Fabric, createFabric, createReactFabric } from "@kubb/react-fabric";
|
|
10
8
|
import { typescriptParser } from "@kubb/react-fabric/parsers";
|
|
11
9
|
import { fsPlugin } from "@kubb/react-fabric/plugins";
|
|
12
10
|
import { performance } from "node:perf_hooks";
|
|
13
11
|
import { deflateSync } from "fflate";
|
|
14
12
|
import { x } from "tinyexec";
|
|
13
|
+
import { jsx } from "@kubb/react-fabric/jsx-runtime";
|
|
15
14
|
import { version } from "node:process";
|
|
16
|
-
import
|
|
17
|
-
import { pathToFileURL } from "node:url";
|
|
15
|
+
import { sortBy } from "remeda";
|
|
18
16
|
import * as pkg from "empathic/package";
|
|
19
17
|
import { coerce, satisfies } from "semver";
|
|
20
|
-
|
|
21
|
-
//#region ../../internals/utils/dist/index.js
|
|
22
|
-
/** Thrown when a plugin's configuration or input fails validation. */
|
|
23
|
-
var ValidationPluginError = class extends Error {};
|
|
18
|
+
//#region ../../internals/utils/src/errors.ts
|
|
24
19
|
/**
|
|
25
20
|
* Thrown when one or more errors occur during a Kubb build.
|
|
26
21
|
* Carries the full list of underlying errors on `errors`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* throw new BuildError('Build failed', { errors: [err1, err2] })
|
|
26
|
+
* ```
|
|
27
27
|
*/
|
|
28
28
|
var BuildError = class extends Error {
|
|
29
29
|
errors;
|
|
@@ -35,19 +35,34 @@ var BuildError = class extends Error {
|
|
|
35
35
|
};
|
|
36
36
|
/**
|
|
37
37
|
* Coerces an unknown thrown value to an `Error` instance.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* try { ... } catch(err) {
|
|
43
|
+
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
40
46
|
*/
|
|
41
47
|
function toError(value) {
|
|
42
48
|
return value instanceof Error ? value : new Error(String(value));
|
|
43
49
|
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region ../../internals/utils/src/asyncEventEmitter.ts
|
|
44
52
|
/**
|
|
45
|
-
*
|
|
53
|
+
* Typed `EventEmitter` that awaits all async listeners before resolving.
|
|
46
54
|
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
|
|
59
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
60
|
+
* await emitter.emit('build', 'petstore') // all listeners awaited
|
|
61
|
+
* ```
|
|
47
62
|
*/
|
|
48
63
|
var AsyncEventEmitter = class {
|
|
49
64
|
/**
|
|
50
|
-
*
|
|
65
|
+
* Maximum number of listeners per event before Node emits a memory-leak warning.
|
|
51
66
|
* @default 10
|
|
52
67
|
*/
|
|
53
68
|
constructor(maxListener = 10) {
|
|
@@ -55,31 +70,48 @@ var AsyncEventEmitter = class {
|
|
|
55
70
|
}
|
|
56
71
|
#emitter = new EventEmitter();
|
|
57
72
|
/**
|
|
58
|
-
* Emits
|
|
73
|
+
* Emits `eventName` and awaits all registered listeners sequentially.
|
|
59
74
|
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* await emitter.emit('build', 'petstore')
|
|
79
|
+
* ```
|
|
60
80
|
*/
|
|
61
81
|
async emit(eventName, ...eventArgs) {
|
|
62
82
|
const listeners = this.#emitter.listeners(eventName);
|
|
63
83
|
if (listeners.length === 0) return;
|
|
64
|
-
|
|
84
|
+
for (const listener of listeners) try {
|
|
85
|
+
await listener(...eventArgs);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
let serializedArgs;
|
|
65
88
|
try {
|
|
66
|
-
|
|
67
|
-
} catch
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
serializedArgs = JSON.stringify(eventArgs);
|
|
71
|
-
} catch {
|
|
72
|
-
serializedArgs = String(eventArgs);
|
|
73
|
-
}
|
|
74
|
-
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
|
|
89
|
+
serializedArgs = JSON.stringify(eventArgs);
|
|
90
|
+
} catch {
|
|
91
|
+
serializedArgs = String(eventArgs);
|
|
75
92
|
}
|
|
76
|
-
|
|
93
|
+
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Registers a persistent listener for `eventName`.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
79
104
|
on(eventName, handler) {
|
|
80
105
|
this.#emitter.on(eventName, handler);
|
|
81
106
|
}
|
|
82
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Registers a one-shot listener that removes itself after the first invocation.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* emitter.onOnce('build', async (name) => { console.log(name) })
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
83
115
|
onOnce(eventName, handler) {
|
|
84
116
|
const wrapper = (...args) => {
|
|
85
117
|
this.off(eventName, wrapper);
|
|
@@ -87,15 +119,31 @@ var AsyncEventEmitter = class {
|
|
|
87
119
|
};
|
|
88
120
|
this.on(eventName, wrapper);
|
|
89
121
|
}
|
|
90
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Removes a previously registered listener.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* emitter.off('build', handler)
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
91
130
|
off(eventName, handler) {
|
|
92
131
|
this.#emitter.off(eventName, handler);
|
|
93
132
|
}
|
|
94
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Removes all listeners from every event channel.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* emitter.removeAll()
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
95
141
|
removeAll() {
|
|
96
142
|
this.#emitter.removeAllListeners();
|
|
97
143
|
}
|
|
98
144
|
};
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region ../../internals/utils/src/casing.ts
|
|
99
147
|
/**
|
|
100
148
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
101
149
|
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
@@ -114,9 +162,12 @@ function toCamelOrPascal(text, pascal) {
|
|
|
114
162
|
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
115
163
|
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
116
164
|
* Segments are joined with `/` to form a file path.
|
|
165
|
+
*
|
|
166
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
167
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
117
168
|
*/
|
|
118
169
|
function applyToFileParts(text, transformPart) {
|
|
119
|
-
const parts = text.split(
|
|
170
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
120
171
|
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
121
172
|
}
|
|
122
173
|
/**
|
|
@@ -134,190 +185,33 @@ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
134
185
|
} : {}));
|
|
135
186
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
136
187
|
}
|
|
137
|
-
/** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
|
|
138
|
-
function defineCLIAdapter(adapter) {
|
|
139
|
-
return adapter;
|
|
140
|
-
}
|
|
141
188
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
189
|
+
* Converts `text` to PascalCase.
|
|
190
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
194
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
144
195
|
*/
|
|
145
|
-
function
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
description: def.description,
|
|
152
|
-
arguments: def.arguments,
|
|
153
|
-
options: serializeOptions(def.options ?? {}),
|
|
154
|
-
subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
function serializeOptions(options) {
|
|
158
|
-
return Object.entries(options).map(([name, opt]) => {
|
|
159
|
-
return {
|
|
160
|
-
name,
|
|
161
|
-
flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
|
|
162
|
-
type: opt.type,
|
|
163
|
-
description: opt.description,
|
|
164
|
-
...opt.default !== void 0 ? { default: opt.default } : {},
|
|
165
|
-
...opt.hint ? { hint: opt.hint } : {},
|
|
166
|
-
...opt.enum ? { enum: opt.enum } : {},
|
|
167
|
-
...opt.required ? { required: opt.required } : {}
|
|
168
|
-
};
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
/** Prints formatted help output for a command using its `CommandDefinition`. */
|
|
172
|
-
function renderHelp(def, parentName) {
|
|
173
|
-
const schema = getCommandSchema([def])[0];
|
|
174
|
-
const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
|
|
175
|
-
const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
|
|
176
|
-
const subCmdPart = schema.subCommands.length ? " <command>" : "";
|
|
177
|
-
console.log(`\n${styleText("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
|
|
178
|
-
if (schema.description) console.log(` ${schema.description}\n`);
|
|
179
|
-
if (schema.subCommands.length) {
|
|
180
|
-
console.log(styleText("bold", "Commands:"));
|
|
181
|
-
for (const sub of schema.subCommands) console.log(` ${styleText("cyan", sub.name.padEnd(16))}${sub.description}`);
|
|
182
|
-
console.log();
|
|
183
|
-
}
|
|
184
|
-
const options = [...schema.options, {
|
|
185
|
-
name: "help",
|
|
186
|
-
flags: "-h, --help",
|
|
187
|
-
type: "boolean",
|
|
188
|
-
description: "Show help"
|
|
189
|
-
}];
|
|
190
|
-
console.log(styleText("bold", "Options:"));
|
|
191
|
-
for (const opt of options) {
|
|
192
|
-
const flags = styleText("cyan", opt.flags.padEnd(30));
|
|
193
|
-
const defaultPart = opt.default !== void 0 ? styleText("dim", ` (default: ${opt.default})`) : "";
|
|
194
|
-
console.log(` ${flags}${opt.description}${defaultPart}`);
|
|
195
|
-
}
|
|
196
|
-
console.log();
|
|
197
|
-
}
|
|
198
|
-
function buildParseOptions(def) {
|
|
199
|
-
const result = { help: {
|
|
200
|
-
type: "boolean",
|
|
201
|
-
short: "h"
|
|
202
|
-
} };
|
|
203
|
-
for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
|
|
204
|
-
type: opt.type,
|
|
205
|
-
...opt.short ? { short: opt.short } : {},
|
|
206
|
-
...opt.default !== void 0 ? { default: opt.default } : {}
|
|
207
|
-
};
|
|
208
|
-
return result;
|
|
196
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
197
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
198
|
+
prefix,
|
|
199
|
+
suffix
|
|
200
|
+
}) : camelCase(part));
|
|
201
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
209
202
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let parsed;
|
|
213
|
-
try {
|
|
214
|
-
const result = parseArgs({
|
|
215
|
-
args: argv,
|
|
216
|
-
options: parseOptions,
|
|
217
|
-
allowPositionals: true,
|
|
218
|
-
strict: false
|
|
219
|
-
});
|
|
220
|
-
parsed = {
|
|
221
|
-
values: result.values,
|
|
222
|
-
positionals: result.positionals
|
|
223
|
-
};
|
|
224
|
-
} catch {
|
|
225
|
-
renderHelp(def, parentName);
|
|
226
|
-
process.exit(1);
|
|
227
|
-
}
|
|
228
|
-
if (parsed.values["help"]) {
|
|
229
|
-
renderHelp(def, parentName);
|
|
230
|
-
process.exit(0);
|
|
231
|
-
}
|
|
232
|
-
for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
|
|
233
|
-
console.error(styleText("red", `Error: --${name} is required`));
|
|
234
|
-
renderHelp(def, parentName);
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
|
-
if (!def.run) {
|
|
238
|
-
renderHelp(def, parentName);
|
|
239
|
-
process.exit(0);
|
|
240
|
-
}
|
|
241
|
-
try {
|
|
242
|
-
await def.run(parsed);
|
|
243
|
-
} catch (err) {
|
|
244
|
-
console.error(styleText("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
245
|
-
renderHelp(def, parentName);
|
|
246
|
-
process.exit(1);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
function printRootHelp(programName, version, defs) {
|
|
250
|
-
console.log(`\n${styleText("bold", "Usage:")} ${programName} <command> [options]\n`);
|
|
251
|
-
console.log(` Kubb generation — v${version}\n`);
|
|
252
|
-
console.log(styleText("bold", "Commands:"));
|
|
253
|
-
for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
|
|
254
|
-
console.log();
|
|
255
|
-
console.log(styleText("bold", "Options:"));
|
|
256
|
-
console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
|
|
257
|
-
console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
|
|
258
|
-
console.log();
|
|
259
|
-
console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
|
|
260
|
-
}
|
|
261
|
-
defineCLIAdapter({
|
|
262
|
-
renderHelp(def, parentName) {
|
|
263
|
-
renderHelp(def, parentName);
|
|
264
|
-
},
|
|
265
|
-
async run(defs, argv, opts) {
|
|
266
|
-
const { programName, defaultCommandName, version } = opts;
|
|
267
|
-
const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
|
|
268
|
-
if (args[0] === "--version" || args[0] === "-v") {
|
|
269
|
-
console.log(version);
|
|
270
|
-
process.exit(0);
|
|
271
|
-
}
|
|
272
|
-
if (args[0] === "--help" || args[0] === "-h") {
|
|
273
|
-
printRootHelp(programName, version, defs);
|
|
274
|
-
process.exit(0);
|
|
275
|
-
}
|
|
276
|
-
if (args.length === 0) {
|
|
277
|
-
const defaultDef = defs.find((d) => d.name === defaultCommandName);
|
|
278
|
-
if (defaultDef?.run) await runCommand(defaultDef, [], programName);
|
|
279
|
-
else printRootHelp(programName, version, defs);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const [first, ...rest] = args;
|
|
283
|
-
const isKnownSubcommand = defs.some((d) => d.name === first);
|
|
284
|
-
let def;
|
|
285
|
-
let commandArgv;
|
|
286
|
-
let parentName;
|
|
287
|
-
if (isKnownSubcommand) {
|
|
288
|
-
def = defs.find((d) => d.name === first);
|
|
289
|
-
commandArgv = rest;
|
|
290
|
-
parentName = programName;
|
|
291
|
-
} else {
|
|
292
|
-
def = defs.find((d) => d.name === defaultCommandName);
|
|
293
|
-
commandArgv = args;
|
|
294
|
-
parentName = programName;
|
|
295
|
-
}
|
|
296
|
-
if (!def) {
|
|
297
|
-
console.error(`Unknown command: ${first}`);
|
|
298
|
-
printRootHelp(programName, version, defs);
|
|
299
|
-
process.exit(1);
|
|
300
|
-
}
|
|
301
|
-
if (def.subCommands?.length) {
|
|
302
|
-
const [subName, ...subRest] = commandArgv;
|
|
303
|
-
const subDef = def.subCommands.find((s) => s.name === subName);
|
|
304
|
-
if (subName === "--help" || subName === "-h") {
|
|
305
|
-
renderHelp(def, parentName);
|
|
306
|
-
process.exit(0);
|
|
307
|
-
}
|
|
308
|
-
if (!subDef) {
|
|
309
|
-
renderHelp(def, parentName);
|
|
310
|
-
process.exit(subName ? 1 : 0);
|
|
311
|
-
}
|
|
312
|
-
await runCommand(subDef, subRest, `${parentName} ${def.name}`);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
await runCommand(def, commandArgv, parentName);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region ../../internals/utils/src/time.ts
|
|
318
205
|
/**
|
|
319
|
-
* Calculates elapsed time in milliseconds from a high-resolution start time.
|
|
320
|
-
* Rounds to 2 decimal places
|
|
206
|
+
* Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
|
|
207
|
+
* Rounds to 2 decimal places for sub-millisecond precision without noise.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const start = process.hrtime()
|
|
212
|
+
* doWork()
|
|
213
|
+
* getElapsedMs(start) // 42.35
|
|
214
|
+
* ```
|
|
321
215
|
*/
|
|
322
216
|
function getElapsedMs(hrStart) {
|
|
323
217
|
const [seconds, nanoseconds] = process.hrtime(hrStart);
|
|
@@ -325,39 +219,22 @@ function getElapsedMs(hrStart) {
|
|
|
325
219
|
return Math.round(ms * 100) / 100;
|
|
326
220
|
}
|
|
327
221
|
/**
|
|
328
|
-
* Converts a millisecond duration into a human-readable string.
|
|
329
|
-
*
|
|
222
|
+
* Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* formatMs(250) // '250ms'
|
|
227
|
+
* formatMs(1500) // '1.50s'
|
|
228
|
+
* formatMs(90000) // '1m 30.0s'
|
|
229
|
+
* ```
|
|
330
230
|
*/
|
|
331
231
|
function formatMs(ms) {
|
|
332
232
|
if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
|
|
333
233
|
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
334
234
|
return `${Math.round(ms)}ms`;
|
|
335
235
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
* Falls back to `255` for any channel that cannot be parsed.
|
|
339
|
-
*/
|
|
340
|
-
function parseHex(color) {
|
|
341
|
-
const int = Number.parseInt(color.replace("#", ""), 16);
|
|
342
|
-
return Number.isNaN(int) ? {
|
|
343
|
-
r: 255,
|
|
344
|
-
g: 255,
|
|
345
|
-
b: 255
|
|
346
|
-
} : {
|
|
347
|
-
r: int >> 16 & 255,
|
|
348
|
-
g: int >> 8 & 255,
|
|
349
|
-
b: int & 255
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
|
|
354
|
-
* for the given hex color.
|
|
355
|
-
*/
|
|
356
|
-
function hex(color) {
|
|
357
|
-
const { r, g, b } = parseHex(color);
|
|
358
|
-
return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
|
|
359
|
-
}
|
|
360
|
-
hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region ../../internals/utils/src/fs.ts
|
|
361
238
|
/**
|
|
362
239
|
* Converts all backslashes to forward slashes.
|
|
363
240
|
* Extended-length Windows paths (`\\?\...`) are left unchanged.
|
|
@@ -367,8 +244,14 @@ function toSlash(p) {
|
|
|
367
244
|
return p.replaceAll("\\", "/");
|
|
368
245
|
}
|
|
369
246
|
/**
|
|
370
|
-
* Returns the relative path from `rootDir` to `filePath`, always using
|
|
371
|
-
*
|
|
247
|
+
* Returns the relative path from `rootDir` to `filePath`, always using forward slashes
|
|
248
|
+
* and prefixed with `./` when not already traversing upward.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'
|
|
253
|
+
* getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'
|
|
254
|
+
* ```
|
|
372
255
|
*/
|
|
373
256
|
function getRelativePath(rootDir, filePath) {
|
|
374
257
|
if (!rootDir || !filePath) throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ""} ${filePath || ""}`);
|
|
@@ -378,43 +261,54 @@ function getRelativePath(rootDir, filePath) {
|
|
|
378
261
|
/**
|
|
379
262
|
* Resolves to `true` when the file or directory at `path` exists.
|
|
380
263
|
* Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* if (await exists('./kubb.config.ts')) {
|
|
268
|
+
* const content = await read('./kubb.config.ts')
|
|
269
|
+
* }
|
|
270
|
+
* ```
|
|
381
271
|
*/
|
|
382
272
|
async function exists(path) {
|
|
383
273
|
if (typeof Bun !== "undefined") return Bun.file(path).exists();
|
|
384
274
|
return access(path).then(() => true, () => false);
|
|
385
275
|
}
|
|
386
276
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
277
|
+
* Synchronous counterpart of `read`.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* const source = readSync('./src/Pet.ts')
|
|
282
|
+
* ```
|
|
389
283
|
*/
|
|
390
|
-
async function read(path) {
|
|
391
|
-
if (typeof Bun !== "undefined") return Bun.file(path).text();
|
|
392
|
-
return readFile(path, { encoding: "utf8" });
|
|
393
|
-
}
|
|
394
|
-
/** Synchronous counterpart of `read`. */
|
|
395
284
|
function readSync(path) {
|
|
396
285
|
return readFileSync(path, { encoding: "utf8" });
|
|
397
286
|
}
|
|
398
287
|
/**
|
|
399
288
|
* Writes `data` to `path`, trimming leading/trailing whitespace before saving.
|
|
400
|
-
* Skips the write
|
|
401
|
-
* identical to what is already on disk.
|
|
289
|
+
* Skips the write when the trimmed content is empty or identical to what is already on disk.
|
|
402
290
|
* Creates any missing parent directories automatically.
|
|
403
|
-
* When `sanity` is `true`, re-reads the file after writing and throws if the
|
|
404
|
-
*
|
|
291
|
+
* When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* await write('./src/Pet.ts', source) // writes and returns trimmed content
|
|
296
|
+
* await write('./src/Pet.ts', source) // null — file unchanged
|
|
297
|
+
* await write('./src/Pet.ts', ' ') // null — empty content skipped
|
|
298
|
+
* ```
|
|
405
299
|
*/
|
|
406
300
|
async function write(path, data, options = {}) {
|
|
407
301
|
const trimmed = data.trim();
|
|
408
|
-
if (trimmed === "") return
|
|
302
|
+
if (trimmed === "") return null;
|
|
409
303
|
const resolved = resolve(path);
|
|
410
304
|
if (typeof Bun !== "undefined") {
|
|
411
305
|
const file = Bun.file(resolved);
|
|
412
|
-
if ((await file.exists() ? await file.text() : null) === trimmed) return
|
|
306
|
+
if ((await file.exists() ? await file.text() : null) === trimmed) return null;
|
|
413
307
|
await Bun.write(resolved, trimmed);
|
|
414
308
|
return trimmed;
|
|
415
309
|
}
|
|
416
310
|
try {
|
|
417
|
-
if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return
|
|
311
|
+
if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return null;
|
|
418
312
|
} catch {}
|
|
419
313
|
await mkdir(dirname(resolved), { recursive: true });
|
|
420
314
|
await writeFile(resolved, trimmed, { encoding: "utf-8" });
|
|
@@ -425,31 +319,40 @@ async function write(path, data, options = {}) {
|
|
|
425
319
|
}
|
|
426
320
|
return trimmed;
|
|
427
321
|
}
|
|
428
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* Recursively removes `path`. Silently succeeds when `path` does not exist.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* await clean('./dist')
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
429
330
|
async function clean(path) {
|
|
430
331
|
return rm(path, {
|
|
431
332
|
recursive: true,
|
|
432
333
|
force: true
|
|
433
334
|
});
|
|
434
335
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region ../../internals/utils/src/promise.ts
|
|
338
|
+
/** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* const results = await Promise.allSettled([p1, p2])
|
|
343
|
+
* results.filter(isPromiseRejectedResult<Error>).map((r) => r.reason.message)
|
|
344
|
+
* ```
|
|
438
345
|
*/
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
if (used) {
|
|
442
|
-
data[originalName] = ++used;
|
|
443
|
-
return originalName;
|
|
444
|
-
}
|
|
445
|
-
data[originalName] = 1;
|
|
446
|
-
return originalName;
|
|
346
|
+
function isPromiseRejectedResult(result) {
|
|
347
|
+
return result.status === "rejected";
|
|
447
348
|
}
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
448
351
|
/**
|
|
449
352
|
* JavaScript and Java reserved words.
|
|
450
353
|
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
451
354
|
*/
|
|
452
|
-
const reservedWords = [
|
|
355
|
+
const reservedWords = new Set([
|
|
453
356
|
"abstract",
|
|
454
357
|
"arguments",
|
|
455
358
|
"boolean",
|
|
@@ -531,18 +434,31 @@ const reservedWords = [
|
|
|
531
434
|
"toString",
|
|
532
435
|
"undefined",
|
|
533
436
|
"valueOf"
|
|
534
|
-
];
|
|
437
|
+
]);
|
|
535
438
|
/**
|
|
536
|
-
* Prefixes
|
|
537
|
-
*
|
|
439
|
+
* Prefixes `word` with `_` when it is a reserved JavaScript/Java identifier or starts with a digit.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* transformReservedWord('class') // '_class'
|
|
444
|
+
* transformReservedWord('42foo') // '_42foo'
|
|
445
|
+
* transformReservedWord('status') // 'status'
|
|
446
|
+
* ```
|
|
538
447
|
*/
|
|
539
448
|
function transformReservedWord(word) {
|
|
540
449
|
const firstChar = word.charCodeAt(0);
|
|
541
|
-
if (word && (reservedWords.
|
|
450
|
+
if (word && (reservedWords.has(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
|
|
542
451
|
return word;
|
|
543
452
|
}
|
|
544
453
|
/**
|
|
545
454
|
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* ```ts
|
|
458
|
+
* isValidVarName('status') // true
|
|
459
|
+
* isValidVarName('class') // false (reserved word)
|
|
460
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
461
|
+
* ```
|
|
546
462
|
*/
|
|
547
463
|
function isValidVarName(name) {
|
|
548
464
|
try {
|
|
@@ -552,6 +468,8 @@ function isValidVarName(name) {
|
|
|
552
468
|
}
|
|
553
469
|
return true;
|
|
554
470
|
}
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region ../../internals/utils/src/urlPath.ts
|
|
555
473
|
/**
|
|
556
474
|
* Parses and transforms an OpenAPI/Swagger path string into various URL formats.
|
|
557
475
|
*
|
|
@@ -561,18 +479,33 @@ function isValidVarName(name) {
|
|
|
561
479
|
* p.template // '`/pet/${petId}`'
|
|
562
480
|
*/
|
|
563
481
|
var URLPath = class {
|
|
564
|
-
/**
|
|
482
|
+
/**
|
|
483
|
+
* The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
|
|
484
|
+
*/
|
|
565
485
|
path;
|
|
566
486
|
#options;
|
|
567
487
|
constructor(path, options = {}) {
|
|
568
488
|
this.path = path;
|
|
569
489
|
this.#options = options;
|
|
570
490
|
}
|
|
571
|
-
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
491
|
+
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```ts
|
|
495
|
+
* new URLPath('/pet/{petId}').URL // '/pet/:petId'
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
572
498
|
get URL() {
|
|
573
499
|
return this.toURLPath();
|
|
574
500
|
}
|
|
575
|
-
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
501
|
+
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```ts
|
|
505
|
+
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
506
|
+
* new URLPath('/pet/{petId}').isURL // false
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
576
509
|
get isURL() {
|
|
577
510
|
try {
|
|
578
511
|
return !!new URL(this.path).href;
|
|
@@ -590,11 +523,25 @@ var URLPath = class {
|
|
|
590
523
|
get template() {
|
|
591
524
|
return this.toTemplateString();
|
|
592
525
|
}
|
|
593
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
526
|
+
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```ts
|
|
530
|
+
* new URLPath('/pet/{petId}').object
|
|
531
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
594
534
|
get object() {
|
|
595
535
|
return this.toObject();
|
|
596
536
|
}
|
|
597
|
-
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
537
|
+
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* ```ts
|
|
541
|
+
* new URLPath('/pet/{petId}').params // { petId: 'petId' }
|
|
542
|
+
* new URLPath('/pet').params // undefined
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
598
545
|
get params() {
|
|
599
546
|
return this.getParams();
|
|
600
547
|
}
|
|
@@ -602,7 +549,9 @@ var URLPath = class {
|
|
|
602
549
|
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
603
550
|
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
604
551
|
}
|
|
605
|
-
/**
|
|
552
|
+
/**
|
|
553
|
+
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
554
|
+
*/
|
|
606
555
|
#eachParam(fn) {
|
|
607
556
|
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
608
557
|
const raw = match[1];
|
|
@@ -639,6 +588,12 @@ var URLPath = class {
|
|
|
639
588
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
640
589
|
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
641
590
|
* Returns `undefined` when no path parameters are found.
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```ts
|
|
594
|
+
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
595
|
+
* // { petId: 'petId', tagId: 'tagId' }
|
|
596
|
+
* ```
|
|
642
597
|
*/
|
|
643
598
|
getParams(replacer) {
|
|
644
599
|
const params = {};
|
|
@@ -648,7 +603,13 @@ var URLPath = class {
|
|
|
648
603
|
});
|
|
649
604
|
return Object.keys(params).length > 0 ? params : void 0;
|
|
650
605
|
}
|
|
651
|
-
/** Converts the OpenAPI path to Express-style colon syntax
|
|
606
|
+
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
611
|
+
* ```
|
|
612
|
+
*/
|
|
652
613
|
toURLPath() {
|
|
653
614
|
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
654
615
|
}
|
|
@@ -666,11 +627,27 @@ function isInputPath(config) {
|
|
|
666
627
|
}
|
|
667
628
|
//#endregion
|
|
668
629
|
//#region src/constants.ts
|
|
630
|
+
/**
|
|
631
|
+
* Base URL for the Kubb Studio web app.
|
|
632
|
+
*/
|
|
669
633
|
const DEFAULT_STUDIO_URL = "https://studio.kubb.dev";
|
|
634
|
+
/**
|
|
635
|
+
* File name used for generated barrel (index) files.
|
|
636
|
+
*/
|
|
670
637
|
const BARREL_FILENAME = "index.ts";
|
|
638
|
+
/**
|
|
639
|
+
* Default banner style written at the top of every generated file.
|
|
640
|
+
*/
|
|
671
641
|
const DEFAULT_BANNER = "simple";
|
|
642
|
+
/**
|
|
643
|
+
* Default file-extension mapping used when no explicit mapping is configured.
|
|
644
|
+
*/
|
|
672
645
|
const DEFAULT_EXTENSION = { ".ts": ".ts" };
|
|
673
|
-
|
|
646
|
+
/**
|
|
647
|
+
* Numeric log-level thresholds used internally to compare verbosity.
|
|
648
|
+
*
|
|
649
|
+
* Higher numbers are more verbose.
|
|
650
|
+
*/
|
|
674
651
|
const logLevel = {
|
|
675
652
|
silent: Number.NEGATIVE_INFINITY,
|
|
676
653
|
error: 0,
|
|
@@ -679,6 +656,13 @@ const logLevel = {
|
|
|
679
656
|
verbose: 4,
|
|
680
657
|
debug: 5
|
|
681
658
|
};
|
|
659
|
+
/**
|
|
660
|
+
* CLI command descriptors for each supported linter.
|
|
661
|
+
*
|
|
662
|
+
* Each entry contains the executable `command`, an `args` factory that maps an
|
|
663
|
+
* output path to the correct argument list, and an `errorMessage` shown when
|
|
664
|
+
* the linter is not found.
|
|
665
|
+
*/
|
|
682
666
|
const linters = {
|
|
683
667
|
eslint: {
|
|
684
668
|
command: "eslint",
|
|
@@ -700,6 +684,13 @@ const linters = {
|
|
|
700
684
|
errorMessage: "Oxlint not found"
|
|
701
685
|
}
|
|
702
686
|
};
|
|
687
|
+
/**
|
|
688
|
+
* CLI command descriptors for each supported code formatter.
|
|
689
|
+
*
|
|
690
|
+
* Each entry contains the executable `command`, an `args` factory that maps an
|
|
691
|
+
* output path to the correct argument list, and an `errorMessage` shown when
|
|
692
|
+
* the formatter is not found.
|
|
693
|
+
*/
|
|
703
694
|
const formatters = {
|
|
704
695
|
prettier: {
|
|
705
696
|
command: "prettier",
|
|
@@ -899,7 +890,12 @@ function validateConcurrency(concurrency) {
|
|
|
899
890
|
//#endregion
|
|
900
891
|
//#region src/utils/executeStrategies.ts
|
|
901
892
|
/**
|
|
902
|
-
*
|
|
893
|
+
* Runs promise functions in sequence, threading each result into the next call.
|
|
894
|
+
*
|
|
895
|
+
* - Each function receives the accumulated state from the previous call.
|
|
896
|
+
* - Skips functions that return a falsy value (acts as a no-op for that step).
|
|
897
|
+
* - Returns an array of all individual results.
|
|
898
|
+
* @deprecated
|
|
903
899
|
*/
|
|
904
900
|
function hookSeq(promises) {
|
|
905
901
|
return promises.filter(Boolean).reduce((promise, func) => {
|
|
@@ -912,7 +908,11 @@ function hookSeq(promises) {
|
|
|
912
908
|
}, Promise.resolve([]));
|
|
913
909
|
}
|
|
914
910
|
/**
|
|
915
|
-
*
|
|
911
|
+
* Runs promise functions in sequence and returns the first non-null result.
|
|
912
|
+
*
|
|
913
|
+
* - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
|
|
914
|
+
* - Subsequent functions are skipped once a match is found.
|
|
915
|
+
* @deprecated
|
|
916
916
|
*/
|
|
917
917
|
function hookFirst(promises, nullCheck = (state) => state !== null) {
|
|
918
918
|
let promise = Promise.resolve(null);
|
|
@@ -923,7 +923,11 @@ function hookFirst(promises, nullCheck = (state) => state !== null) {
|
|
|
923
923
|
return promise;
|
|
924
924
|
}
|
|
925
925
|
/**
|
|
926
|
-
* Runs
|
|
926
|
+
* Runs promise functions concurrently and returns all settled results.
|
|
927
|
+
*
|
|
928
|
+
* - Limits simultaneous executions to `concurrency` (default: unlimited).
|
|
929
|
+
* - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
|
|
930
|
+
* @deprecated
|
|
927
931
|
*/
|
|
928
932
|
function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
|
|
929
933
|
const limit = pLimit(concurrency);
|
|
@@ -931,29 +935,22 @@ function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
|
|
|
931
935
|
return Promise.allSettled(tasks);
|
|
932
936
|
}
|
|
933
937
|
//#endregion
|
|
934
|
-
//#region src/
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
throw new Error(`${strategy} not implemented`);
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
function isPromiseRejectedResult(result) {
|
|
948
|
-
return result.status === "rejected";
|
|
949
|
-
}
|
|
950
|
-
//#endregion
|
|
951
|
-
//#region src/PluginManager.ts
|
|
938
|
+
//#region src/PluginDriver.ts
|
|
939
|
+
/**
|
|
940
|
+
* Returns `'single'` when `fileOrFolder` has a file extension, `'split'` otherwise.
|
|
941
|
+
*
|
|
942
|
+
* @example
|
|
943
|
+
* ```ts
|
|
944
|
+
* getMode('src/gen/types.ts') // 'single'
|
|
945
|
+
* getMode('src/gen/types') // 'split'
|
|
946
|
+
* ```
|
|
947
|
+
*/
|
|
952
948
|
function getMode(fileOrFolder) {
|
|
953
949
|
if (!fileOrFolder) return "split";
|
|
954
950
|
return extname(fileOrFolder) ? "single" : "split";
|
|
955
951
|
}
|
|
956
|
-
|
|
952
|
+
const hookFirstNullCheck = (state) => !!state?.result;
|
|
953
|
+
var PluginDriver = class {
|
|
957
954
|
config;
|
|
958
955
|
options;
|
|
959
956
|
/**
|
|
@@ -963,31 +960,43 @@ var PluginManager = class {
|
|
|
963
960
|
rootNode = void 0;
|
|
964
961
|
adapter = void 0;
|
|
965
962
|
#studioIsOpen = false;
|
|
966
|
-
|
|
967
|
-
#usedPluginNames = {};
|
|
968
|
-
#promiseManager;
|
|
963
|
+
plugins = /* @__PURE__ */ new Map();
|
|
969
964
|
constructor(config, options) {
|
|
970
965
|
this.config = config;
|
|
971
966
|
this.options = options;
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
967
|
+
config.plugins.map((plugin) => Object.assign({
|
|
968
|
+
buildStart() {},
|
|
969
|
+
buildEnd() {}
|
|
970
|
+
}, plugin)).filter((plugin) => {
|
|
971
|
+
if (typeof plugin.apply === "function") return plugin.apply(config);
|
|
972
|
+
return true;
|
|
973
|
+
}).sort((a, b) => {
|
|
974
|
+
if (b.pre?.includes(a.name)) return 1;
|
|
975
|
+
if (b.post?.includes(a.name)) return -1;
|
|
976
|
+
return 0;
|
|
977
|
+
}).forEach((plugin) => {
|
|
978
|
+
this.plugins.set(plugin.name, plugin);
|
|
976
979
|
});
|
|
977
980
|
}
|
|
978
981
|
get events() {
|
|
979
982
|
return this.options.events;
|
|
980
983
|
}
|
|
981
984
|
getContext(plugin) {
|
|
982
|
-
const
|
|
983
|
-
const pluginManager = this;
|
|
985
|
+
const driver = this;
|
|
984
986
|
const baseContext = {
|
|
985
|
-
fabric:
|
|
986
|
-
config:
|
|
987
|
+
fabric: driver.options.fabric,
|
|
988
|
+
config: driver.config,
|
|
989
|
+
get root() {
|
|
990
|
+
return resolve(driver.config.root, driver.config.output.path);
|
|
991
|
+
},
|
|
992
|
+
getMode(output) {
|
|
993
|
+
return getMode(resolve(driver.config.root, driver.config.output.path, output.path));
|
|
994
|
+
},
|
|
995
|
+
events: driver.options.events,
|
|
987
996
|
plugin,
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
997
|
+
getPlugin: driver.getPlugin.bind(driver),
|
|
998
|
+
requirePlugin: driver.requirePlugin.bind(driver),
|
|
999
|
+
driver,
|
|
991
1000
|
addFile: async (...files) => {
|
|
992
1001
|
await this.options.fabric.addFile(...files);
|
|
993
1002
|
},
|
|
@@ -995,23 +1004,38 @@ var PluginManager = class {
|
|
|
995
1004
|
await this.options.fabric.upsertFile(...files);
|
|
996
1005
|
},
|
|
997
1006
|
get rootNode() {
|
|
998
|
-
return
|
|
1007
|
+
return driver.rootNode;
|
|
999
1008
|
},
|
|
1000
1009
|
get adapter() {
|
|
1001
|
-
return
|
|
1010
|
+
return driver.adapter;
|
|
1011
|
+
},
|
|
1012
|
+
get resolver() {
|
|
1013
|
+
return plugin.resolver;
|
|
1014
|
+
},
|
|
1015
|
+
get transformer() {
|
|
1016
|
+
return plugin.transformer;
|
|
1017
|
+
},
|
|
1018
|
+
warn(message) {
|
|
1019
|
+
driver.events.emit("warn", message);
|
|
1020
|
+
},
|
|
1021
|
+
error(error) {
|
|
1022
|
+
driver.events.emit("error", typeof error === "string" ? new Error(error) : error);
|
|
1023
|
+
},
|
|
1024
|
+
info(message) {
|
|
1025
|
+
driver.events.emit("info", message);
|
|
1002
1026
|
},
|
|
1003
1027
|
openInStudio(options) {
|
|
1004
|
-
if (!
|
|
1005
|
-
if (typeof
|
|
1006
|
-
if (!
|
|
1007
|
-
|
|
1008
|
-
const studioUrl =
|
|
1009
|
-
return openInStudio(
|
|
1028
|
+
if (!driver.config.devtools || driver.#studioIsOpen) return;
|
|
1029
|
+
if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
|
|
1030
|
+
if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
|
|
1031
|
+
driver.#studioIsOpen = true;
|
|
1032
|
+
const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
|
|
1033
|
+
return openInStudio(driver.rootNode, studioUrl, options);
|
|
1010
1034
|
}
|
|
1011
1035
|
};
|
|
1012
1036
|
const mergedExtras = {};
|
|
1013
|
-
for (const p of plugins) if (typeof p.inject === "function") {
|
|
1014
|
-
const result = p.inject.call(baseContext
|
|
1037
|
+
for (const p of this.plugins.values()) if (typeof p.inject === "function") {
|
|
1038
|
+
const result = p.inject.call(baseContext);
|
|
1015
1039
|
if (result !== null && typeof result === "object") Object.assign(mergedExtras, result);
|
|
1016
1040
|
}
|
|
1017
1041
|
return {
|
|
@@ -1019,9 +1043,9 @@ var PluginManager = class {
|
|
|
1019
1043
|
...mergedExtras
|
|
1020
1044
|
};
|
|
1021
1045
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1046
|
+
/**
|
|
1047
|
+
* @deprecated use resolvers context instead
|
|
1048
|
+
*/
|
|
1025
1049
|
getFile({ name, mode, extname, pluginName, options }) {
|
|
1026
1050
|
const resolvedName = mode ? mode === "single" ? "" : this.resolveName({
|
|
1027
1051
|
name,
|
|
@@ -1044,6 +1068,9 @@ var PluginManager = class {
|
|
|
1044
1068
|
exports: []
|
|
1045
1069
|
};
|
|
1046
1070
|
}
|
|
1071
|
+
/**
|
|
1072
|
+
* @deprecated use resolvers context instead
|
|
1073
|
+
*/
|
|
1047
1074
|
resolvePath = (params) => {
|
|
1048
1075
|
const defaultPath = resolve(resolve(this.config.root, this.config.output.path), params.baseName);
|
|
1049
1076
|
if (params.pluginName) return this.hookForPluginSync({
|
|
@@ -1064,15 +1091,15 @@ var PluginManager = class {
|
|
|
1064
1091
|
]
|
|
1065
1092
|
})?.result || defaultPath;
|
|
1066
1093
|
};
|
|
1094
|
+
/**
|
|
1095
|
+
* @deprecated use resolvers context instead
|
|
1096
|
+
*/
|
|
1067
1097
|
resolveName = (params) => {
|
|
1068
|
-
if (params.pluginName) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
});
|
|
1074
|
-
return transformReservedWord([...new Set(names)].at(0) || params.name);
|
|
1075
|
-
}
|
|
1098
|
+
if (params.pluginName) return transformReservedWord(this.hookForPluginSync({
|
|
1099
|
+
pluginName: params.pluginName,
|
|
1100
|
+
hookName: "resolveName",
|
|
1101
|
+
parameters: [params.name.trim(), params.type]
|
|
1102
|
+
})?.at(0) ?? params.name);
|
|
1076
1103
|
const name = this.hookFirstSync({
|
|
1077
1104
|
hookName: "resolveName",
|
|
1078
1105
|
parameters: [params.name.trim(), params.type]
|
|
@@ -1083,49 +1110,46 @@ var PluginManager = class {
|
|
|
1083
1110
|
* Run a specific hookName for plugin x.
|
|
1084
1111
|
*/
|
|
1085
1112
|
async hookForPlugin({ pluginName, hookName, parameters }) {
|
|
1086
|
-
const
|
|
1113
|
+
const plugin = this.plugins.get(pluginName);
|
|
1114
|
+
if (!plugin) return [null];
|
|
1087
1115
|
this.events.emit("plugins:hook:progress:start", {
|
|
1088
1116
|
hookName,
|
|
1089
|
-
plugins
|
|
1117
|
+
plugins: [plugin]
|
|
1118
|
+
});
|
|
1119
|
+
const result = await this.#execute({
|
|
1120
|
+
strategy: "hookFirst",
|
|
1121
|
+
hookName,
|
|
1122
|
+
parameters,
|
|
1123
|
+
plugin
|
|
1090
1124
|
});
|
|
1091
|
-
const items = [];
|
|
1092
|
-
for (const plugin of plugins) {
|
|
1093
|
-
const result = await this.#execute({
|
|
1094
|
-
strategy: "hookFirst",
|
|
1095
|
-
hookName,
|
|
1096
|
-
parameters,
|
|
1097
|
-
plugin
|
|
1098
|
-
});
|
|
1099
|
-
if (result !== void 0 && result !== null) items.push(result);
|
|
1100
|
-
}
|
|
1101
1125
|
this.events.emit("plugins:hook:progress:end", { hookName });
|
|
1102
|
-
return
|
|
1126
|
+
return [result];
|
|
1103
1127
|
}
|
|
1104
1128
|
/**
|
|
1105
1129
|
* Run a specific hookName for plugin x.
|
|
1106
1130
|
*/
|
|
1107
1131
|
hookForPluginSync({ pluginName, hookName, parameters }) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
})
|
|
1132
|
+
const plugin = this.plugins.get(pluginName);
|
|
1133
|
+
if (!plugin) return null;
|
|
1134
|
+
const result = this.#executeSync({
|
|
1135
|
+
strategy: "hookFirst",
|
|
1136
|
+
hookName,
|
|
1137
|
+
parameters,
|
|
1138
|
+
plugin
|
|
1139
|
+
});
|
|
1140
|
+
return result !== null ? [result] : [];
|
|
1116
1141
|
}
|
|
1117
1142
|
/**
|
|
1118
1143
|
* Returns the first non-null result.
|
|
1119
1144
|
*/
|
|
1120
1145
|
async hookFirst({ hookName, parameters, skipped }) {
|
|
1121
|
-
const plugins =
|
|
1122
|
-
|
|
1123
|
-
});
|
|
1146
|
+
const plugins = [];
|
|
1147
|
+
for (const plugin of this.plugins.values()) if (hookName in plugin && (skipped ? !skipped.has(plugin) : true)) plugins.push(plugin);
|
|
1124
1148
|
this.events.emit("plugins:hook:progress:start", {
|
|
1125
1149
|
hookName,
|
|
1126
1150
|
plugins
|
|
1127
1151
|
});
|
|
1128
|
-
const
|
|
1152
|
+
const result = await hookFirst(plugins.map((plugin) => {
|
|
1129
1153
|
return async () => {
|
|
1130
1154
|
const value = await this.#execute({
|
|
1131
1155
|
strategy: "hookFirst",
|
|
@@ -1138,8 +1162,7 @@ var PluginManager = class {
|
|
|
1138
1162
|
result: value
|
|
1139
1163
|
});
|
|
1140
1164
|
};
|
|
1141
|
-
});
|
|
1142
|
-
const result = await this.#promiseManager.run("first", promises);
|
|
1165
|
+
}), hookFirstNullCheck);
|
|
1143
1166
|
this.events.emit("plugins:hook:progress:end", { hookName });
|
|
1144
1167
|
return result;
|
|
1145
1168
|
}
|
|
@@ -1148,10 +1171,9 @@ var PluginManager = class {
|
|
|
1148
1171
|
*/
|
|
1149
1172
|
hookFirstSync({ hookName, parameters, skipped }) {
|
|
1150
1173
|
let parseResult = null;
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
for (const plugin of plugins) {
|
|
1174
|
+
for (const plugin of this.plugins.values()) {
|
|
1175
|
+
if (!(hookName in plugin)) continue;
|
|
1176
|
+
if (skipped?.has(plugin)) continue;
|
|
1155
1177
|
parseResult = {
|
|
1156
1178
|
result: this.#executeSync({
|
|
1157
1179
|
strategy: "hookFirst",
|
|
@@ -1161,7 +1183,7 @@ var PluginManager = class {
|
|
|
1161
1183
|
}),
|
|
1162
1184
|
plugin
|
|
1163
1185
|
};
|
|
1164
|
-
if (parseResult
|
|
1186
|
+
if (parseResult.result != null) break;
|
|
1165
1187
|
}
|
|
1166
1188
|
return parseResult;
|
|
1167
1189
|
}
|
|
@@ -1169,13 +1191,14 @@ var PluginManager = class {
|
|
|
1169
1191
|
* Runs all plugins in parallel based on `this.plugin` order and `pre`/`post` settings.
|
|
1170
1192
|
*/
|
|
1171
1193
|
async hookParallel({ hookName, parameters }) {
|
|
1172
|
-
const plugins =
|
|
1194
|
+
const plugins = [];
|
|
1195
|
+
for (const plugin of this.plugins.values()) if (hookName in plugin) plugins.push(plugin);
|
|
1173
1196
|
this.events.emit("plugins:hook:progress:start", {
|
|
1174
1197
|
hookName,
|
|
1175
1198
|
plugins
|
|
1176
1199
|
});
|
|
1177
1200
|
const pluginStartTimes = /* @__PURE__ */ new Map();
|
|
1178
|
-
const
|
|
1201
|
+
const results = await hookParallel(plugins.map((plugin) => {
|
|
1179
1202
|
return () => {
|
|
1180
1203
|
pluginStartTimes.set(plugin, performance.now());
|
|
1181
1204
|
return this.#execute({
|
|
@@ -1185,11 +1208,10 @@ var PluginManager = class {
|
|
|
1185
1208
|
plugin
|
|
1186
1209
|
});
|
|
1187
1210
|
};
|
|
1188
|
-
});
|
|
1189
|
-
const results = await this.#promiseManager.run("parallel", promises, { concurrency: this.options.concurrency });
|
|
1211
|
+
}), this.options.concurrency);
|
|
1190
1212
|
results.forEach((result, index) => {
|
|
1191
1213
|
if (isPromiseRejectedResult(result)) {
|
|
1192
|
-
const plugin =
|
|
1214
|
+
const plugin = plugins[index];
|
|
1193
1215
|
if (plugin) {
|
|
1194
1216
|
const startTime = pluginStartTimes.get(plugin) ?? performance.now();
|
|
1195
1217
|
this.events.emit("error", result.reason, {
|
|
@@ -1212,49 +1234,29 @@ var PluginManager = class {
|
|
|
1212
1234
|
* Chains plugins
|
|
1213
1235
|
*/
|
|
1214
1236
|
async hookSeq({ hookName, parameters }) {
|
|
1215
|
-
const plugins =
|
|
1237
|
+
const plugins = [];
|
|
1238
|
+
for (const plugin of this.plugins.values()) if (hookName in plugin) plugins.push(plugin);
|
|
1216
1239
|
this.events.emit("plugins:hook:progress:start", {
|
|
1217
1240
|
hookName,
|
|
1218
1241
|
plugins
|
|
1219
1242
|
});
|
|
1220
|
-
|
|
1243
|
+
await hookSeq(plugins.map((plugin) => {
|
|
1221
1244
|
return () => this.#execute({
|
|
1222
1245
|
strategy: "hookSeq",
|
|
1223
1246
|
hookName,
|
|
1224
1247
|
parameters,
|
|
1225
1248
|
plugin
|
|
1226
1249
|
});
|
|
1227
|
-
});
|
|
1228
|
-
await this.#promiseManager.run("seq", promises);
|
|
1250
|
+
}));
|
|
1229
1251
|
this.events.emit("plugins:hook:progress:end", { hookName });
|
|
1230
1252
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (hookName) return plugins.filter((plugin) => hookName in plugin);
|
|
1234
|
-
return plugins.map((plugin) => {
|
|
1235
|
-
if (plugin.pre) {
|
|
1236
|
-
let missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
|
|
1237
|
-
if (missingPlugins.includes("plugin-oas") && this.adapter) missingPlugins = missingPlugins.filter((pluginName) => pluginName !== "plugin-oas");
|
|
1238
|
-
if (missingPlugins.length > 0) throw new ValidationPluginError(`The plugin '${plugin.name}' has a pre set that references missing plugins for '${missingPlugins.join(", ")}'`);
|
|
1239
|
-
}
|
|
1240
|
-
return plugin;
|
|
1241
|
-
}).sort((a, b) => {
|
|
1242
|
-
if (b.pre?.includes(a.name)) return 1;
|
|
1243
|
-
if (b.post?.includes(a.name)) return -1;
|
|
1244
|
-
return 0;
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
getPluginByName(pluginName) {
|
|
1248
|
-
return [...this.#plugins].find((item) => item.name === pluginName);
|
|
1253
|
+
getPlugin(pluginName) {
|
|
1254
|
+
return this.plugins.get(pluginName);
|
|
1249
1255
|
}
|
|
1250
|
-
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
const corePlugin = plugins.find((plugin) => plugin.name === "core" && hookName in plugin);
|
|
1255
|
-
return corePlugin ? [corePlugin] : [];
|
|
1256
|
-
}
|
|
1257
|
-
return pluginByPluginName;
|
|
1256
|
+
requirePlugin(pluginName) {
|
|
1257
|
+
const plugin = this.plugins.get(pluginName);
|
|
1258
|
+
if (!plugin) throw new Error(`[kubb] Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`);
|
|
1259
|
+
return plugin;
|
|
1258
1260
|
}
|
|
1259
1261
|
/**
|
|
1260
1262
|
* Run an async plugin hook and return the result.
|
|
@@ -1342,31 +1344,34 @@ var PluginManager = class {
|
|
|
1342
1344
|
return null;
|
|
1343
1345
|
}
|
|
1344
1346
|
}
|
|
1345
|
-
#parse(plugin) {
|
|
1346
|
-
const usedPluginNames = this.#usedPluginNames;
|
|
1347
|
-
setUniqueName(plugin.name, usedPluginNames);
|
|
1348
|
-
const usageCount = usedPluginNames[plugin.name];
|
|
1349
|
-
if (usageCount && usageCount > 1) throw new ValidationPluginError(`Duplicate plugin "${plugin.name}" detected. Each plugin can only be used once. Use a different configuration instead of adding multiple instances of the same plugin.`);
|
|
1350
|
-
return {
|
|
1351
|
-
install() {},
|
|
1352
|
-
...plugin
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
1347
|
};
|
|
1356
1348
|
//#endregion
|
|
1357
|
-
//#region src/
|
|
1349
|
+
//#region src/renderNode.tsx
|
|
1358
1350
|
/**
|
|
1359
|
-
*
|
|
1360
|
-
* same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
|
|
1351
|
+
* Handles the return value of a plugin AST hook or generator method.
|
|
1361
1352
|
*
|
|
1362
|
-
*
|
|
1363
|
-
* `
|
|
1353
|
+
* - React element → rendered via an isolated react-fabric context, files merged into `fabric`
|
|
1354
|
+
* - `Array<FabricFile.File>` → upserted directly into `fabric`
|
|
1355
|
+
* - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
|
|
1356
|
+
*/
|
|
1357
|
+
async function applyHookResult(result, fabric) {
|
|
1358
|
+
if (!result) return;
|
|
1359
|
+
if (Array.isArray(result)) {
|
|
1360
|
+
await fabric.upsertFile(...result);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const fabricChild = createReactFabric();
|
|
1364
|
+
await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { children: result }));
|
|
1365
|
+
fabric.context.fileManager.upsert(...fabricChild.files);
|
|
1366
|
+
fabricChild.unmount();
|
|
1367
|
+
}
|
|
1368
|
+
//#endregion
|
|
1369
|
+
//#region src/createStorage.ts
|
|
1370
|
+
/**
|
|
1371
|
+
* Creates a storage factory. Call the returned function with optional options to get the storage instance.
|
|
1364
1372
|
*
|
|
1365
1373
|
* @example
|
|
1366
|
-
*
|
|
1367
|
-
* import { defineStorage } from '@kubb/core'
|
|
1368
|
-
*
|
|
1369
|
-
* export const memoryStorage = defineStorage((_options) => {
|
|
1374
|
+
* export const memoryStorage = createStorage(() => {
|
|
1370
1375
|
* const store = new Map<string, string>()
|
|
1371
1376
|
* return {
|
|
1372
1377
|
* name: 'memory',
|
|
@@ -1374,13 +1379,15 @@ var PluginManager = class {
|
|
|
1374
1379
|
* async getItem(key) { return store.get(key) ?? null },
|
|
1375
1380
|
* async setItem(key, value) { store.set(key, value) },
|
|
1376
1381
|
* async removeItem(key) { store.delete(key) },
|
|
1377
|
-
* async getKeys() {
|
|
1378
|
-
*
|
|
1382
|
+
* async getKeys(base) {
|
|
1383
|
+
* const keys = [...store.keys()]
|
|
1384
|
+
* return base ? keys.filter((k) => k.startsWith(base)) : keys
|
|
1385
|
+
* },
|
|
1386
|
+
* async clear(base) { if (!base) store.clear() },
|
|
1379
1387
|
* }
|
|
1380
1388
|
* })
|
|
1381
|
-
* ```
|
|
1382
1389
|
*/
|
|
1383
|
-
function
|
|
1390
|
+
function createStorage(build) {
|
|
1384
1391
|
return (options) => build(options ?? {});
|
|
1385
1392
|
}
|
|
1386
1393
|
//#endregion
|
|
@@ -1408,7 +1415,7 @@ function defineStorage(build) {
|
|
|
1408
1415
|
* })
|
|
1409
1416
|
* ```
|
|
1410
1417
|
*/
|
|
1411
|
-
const fsStorage =
|
|
1418
|
+
const fsStorage = createStorage(() => ({
|
|
1412
1419
|
name: "fs",
|
|
1413
1420
|
async hasItem(key) {
|
|
1414
1421
|
try {
|
|
@@ -1456,11 +1463,14 @@ const fsStorage = defineStorage(() => ({
|
|
|
1456
1463
|
}));
|
|
1457
1464
|
//#endregion
|
|
1458
1465
|
//#region package.json
|
|
1459
|
-
var version$1 = "5.0.0-alpha.
|
|
1466
|
+
var version$1 = "5.0.0-alpha.30";
|
|
1460
1467
|
//#endregion
|
|
1461
1468
|
//#region src/utils/diagnostics.ts
|
|
1462
1469
|
/**
|
|
1463
|
-
*
|
|
1470
|
+
* Returns a snapshot of the current runtime environment.
|
|
1471
|
+
*
|
|
1472
|
+
* Useful for attaching context to debug logs and error reports so that
|
|
1473
|
+
* issues can be reproduced without manual information gathering.
|
|
1464
1474
|
*/
|
|
1465
1475
|
function getDiagnosticInfo() {
|
|
1466
1476
|
return {
|
|
@@ -1472,7 +1482,253 @@ function getDiagnosticInfo() {
|
|
|
1472
1482
|
};
|
|
1473
1483
|
}
|
|
1474
1484
|
//#endregion
|
|
1485
|
+
//#region src/utils/TreeNode.ts
|
|
1486
|
+
/**
|
|
1487
|
+
* Tree structure used to build per-directory barrel (`index.ts`) files from a
|
|
1488
|
+
* flat list of generated {@link FabricFile.File} entries.
|
|
1489
|
+
*
|
|
1490
|
+
* Each node represents either a directory or a file within the output tree.
|
|
1491
|
+
* Use {@link TreeNode.build} to construct a root node from a file list, then
|
|
1492
|
+
* traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
|
|
1493
|
+
* `*Deep` helpers.
|
|
1494
|
+
*/
|
|
1495
|
+
var TreeNode = class TreeNode {
|
|
1496
|
+
data;
|
|
1497
|
+
parent;
|
|
1498
|
+
children = [];
|
|
1499
|
+
#cachedLeaves = void 0;
|
|
1500
|
+
constructor(data, parent) {
|
|
1501
|
+
this.data = data;
|
|
1502
|
+
this.parent = parent;
|
|
1503
|
+
}
|
|
1504
|
+
addChild(data) {
|
|
1505
|
+
const child = new TreeNode(data, this);
|
|
1506
|
+
if (!this.children) this.children = [];
|
|
1507
|
+
this.children.push(child);
|
|
1508
|
+
return child;
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Returns the root ancestor of this node, walking up via `parent` links.
|
|
1512
|
+
*/
|
|
1513
|
+
get root() {
|
|
1514
|
+
if (!this.parent) return this;
|
|
1515
|
+
return this.parent.root;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Returns all leaf descendants (nodes with no children) of this node.
|
|
1519
|
+
*
|
|
1520
|
+
* Results are cached after the first traversal.
|
|
1521
|
+
*/
|
|
1522
|
+
get leaves() {
|
|
1523
|
+
if (!this.children || this.children.length === 0) return [this];
|
|
1524
|
+
if (this.#cachedLeaves) return this.#cachedLeaves;
|
|
1525
|
+
const leaves = [];
|
|
1526
|
+
for (const child of this.children) leaves.push(...child.leaves);
|
|
1527
|
+
this.#cachedLeaves = leaves;
|
|
1528
|
+
return leaves;
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Visits this node and every descendant in depth-first order.
|
|
1532
|
+
*/
|
|
1533
|
+
forEach(callback) {
|
|
1534
|
+
if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
|
|
1535
|
+
callback(this);
|
|
1536
|
+
for (const child of this.children) child.forEach(callback);
|
|
1537
|
+
return this;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Finds the first leaf that satisfies `predicate`, or `undefined` when none match.
|
|
1541
|
+
*/
|
|
1542
|
+
findDeep(predicate) {
|
|
1543
|
+
if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
|
|
1544
|
+
return this.leaves.find(predicate);
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Calls `callback` for every leaf of this node.
|
|
1548
|
+
*/
|
|
1549
|
+
forEachDeep(callback) {
|
|
1550
|
+
if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
|
|
1551
|
+
this.leaves.forEach(callback);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Returns all leaves that satisfy `callback`.
|
|
1555
|
+
*/
|
|
1556
|
+
filterDeep(callback) {
|
|
1557
|
+
if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
|
|
1558
|
+
return this.leaves.filter(callback);
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Maps every leaf through `callback` and returns the resulting array.
|
|
1562
|
+
*/
|
|
1563
|
+
mapDeep(callback) {
|
|
1564
|
+
if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
|
|
1565
|
+
return this.leaves.map(callback);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Builds a {@link TreeNode} tree from a flat list of files.
|
|
1569
|
+
*
|
|
1570
|
+
* - Filters to files under `root` (when provided) and skips `.json` files.
|
|
1571
|
+
* - Returns `null` when no files match.
|
|
1572
|
+
*/
|
|
1573
|
+
static build(files, root) {
|
|
1574
|
+
try {
|
|
1575
|
+
const filteredTree = buildDirectoryTree(files, root);
|
|
1576
|
+
if (!filteredTree) return null;
|
|
1577
|
+
const treeNode = new TreeNode({
|
|
1578
|
+
name: filteredTree.name,
|
|
1579
|
+
path: filteredTree.path,
|
|
1580
|
+
file: filteredTree.file,
|
|
1581
|
+
type: getMode(filteredTree.path)
|
|
1582
|
+
});
|
|
1583
|
+
const recurse = (node, item) => {
|
|
1584
|
+
const subNode = node.addChild({
|
|
1585
|
+
name: item.name,
|
|
1586
|
+
path: item.path,
|
|
1587
|
+
file: item.file,
|
|
1588
|
+
type: getMode(item.path)
|
|
1589
|
+
});
|
|
1590
|
+
if (item.children?.length) item.children?.forEach((child) => {
|
|
1591
|
+
recurse(subNode, child);
|
|
1592
|
+
});
|
|
1593
|
+
};
|
|
1594
|
+
filteredTree.children?.forEach((child) => {
|
|
1595
|
+
recurse(treeNode, child);
|
|
1596
|
+
});
|
|
1597
|
+
return treeNode;
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
throw new Error("Something went wrong with creating barrel files with the TreeNode class", { cause: error });
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
const normalizePath = (p) => p.replaceAll("\\", "/");
|
|
1604
|
+
function buildDirectoryTree(files, rootFolder = "") {
|
|
1605
|
+
const normalizedRootFolder = normalizePath(rootFolder);
|
|
1606
|
+
const rootPrefix = normalizedRootFolder.endsWith("/") ? normalizedRootFolder : `${normalizedRootFolder}/`;
|
|
1607
|
+
const filteredFiles = files.filter((file) => {
|
|
1608
|
+
const normalizedFilePath = normalizePath(file.path);
|
|
1609
|
+
return rootFolder ? normalizedFilePath.startsWith(rootPrefix) && !normalizedFilePath.endsWith(".json") : !normalizedFilePath.endsWith(".json");
|
|
1610
|
+
});
|
|
1611
|
+
if (filteredFiles.length === 0) return null;
|
|
1612
|
+
const root = {
|
|
1613
|
+
name: rootFolder || "",
|
|
1614
|
+
path: rootFolder || "",
|
|
1615
|
+
children: []
|
|
1616
|
+
};
|
|
1617
|
+
filteredFiles.forEach((file) => {
|
|
1618
|
+
const parts = file.path.slice(rootFolder.length).split("/").filter(Boolean);
|
|
1619
|
+
let currentLevel = root.children;
|
|
1620
|
+
let currentPath = normalizePath(rootFolder);
|
|
1621
|
+
parts.forEach((part, index) => {
|
|
1622
|
+
currentPath = path.posix.join(currentPath, part);
|
|
1623
|
+
let existingNode = currentLevel.find((node) => node.name === part);
|
|
1624
|
+
if (!existingNode) {
|
|
1625
|
+
if (index === parts.length - 1) existingNode = {
|
|
1626
|
+
name: part,
|
|
1627
|
+
file,
|
|
1628
|
+
path: currentPath
|
|
1629
|
+
};
|
|
1630
|
+
else existingNode = {
|
|
1631
|
+
name: part,
|
|
1632
|
+
path: currentPath,
|
|
1633
|
+
children: []
|
|
1634
|
+
};
|
|
1635
|
+
currentLevel.push(existingNode);
|
|
1636
|
+
}
|
|
1637
|
+
if (!existingNode.file) currentLevel = existingNode.children;
|
|
1638
|
+
});
|
|
1639
|
+
});
|
|
1640
|
+
return root;
|
|
1641
|
+
}
|
|
1642
|
+
//#endregion
|
|
1643
|
+
//#region src/utils/getBarrelFiles.ts
|
|
1644
|
+
/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
|
|
1645
|
+
function getBarrelFilesByRoot(root, files) {
|
|
1646
|
+
const cachedFiles = /* @__PURE__ */ new Map();
|
|
1647
|
+
TreeNode.build(files, root)?.forEach((treeNode) => {
|
|
1648
|
+
if (!treeNode?.children || !treeNode.parent?.data.path) return;
|
|
1649
|
+
const barrelFile = {
|
|
1650
|
+
path: join(treeNode.parent?.data.path, "index.ts"),
|
|
1651
|
+
baseName: "index.ts",
|
|
1652
|
+
exports: [],
|
|
1653
|
+
imports: [],
|
|
1654
|
+
sources: []
|
|
1655
|
+
};
|
|
1656
|
+
const previousBarrelFile = cachedFiles.get(barrelFile.path);
|
|
1657
|
+
treeNode.leaves.forEach((item) => {
|
|
1658
|
+
if (!item.data.name) return;
|
|
1659
|
+
(item.data.file?.sources || []).forEach((source) => {
|
|
1660
|
+
if (!item.data.file?.path || !source.isIndexable || !source.name) return;
|
|
1661
|
+
if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
|
|
1662
|
+
barrelFile.exports.push({
|
|
1663
|
+
name: [source.name],
|
|
1664
|
+
path: getRelativePath(treeNode.parent?.data.path, item.data.path),
|
|
1665
|
+
isTypeOnly: source.isTypeOnly
|
|
1666
|
+
});
|
|
1667
|
+
barrelFile.sources.push({
|
|
1668
|
+
name: source.name,
|
|
1669
|
+
isTypeOnly: source.isTypeOnly,
|
|
1670
|
+
value: "",
|
|
1671
|
+
isExportable: false,
|
|
1672
|
+
isIndexable: false
|
|
1673
|
+
});
|
|
1674
|
+
});
|
|
1675
|
+
});
|
|
1676
|
+
if (previousBarrelFile) {
|
|
1677
|
+
previousBarrelFile.sources.push(...barrelFile.sources);
|
|
1678
|
+
previousBarrelFile.exports?.push(...barrelFile.exports || []);
|
|
1679
|
+
} else cachedFiles.set(barrelFile.path, barrelFile);
|
|
1680
|
+
});
|
|
1681
|
+
return [...cachedFiles.values()];
|
|
1682
|
+
}
|
|
1683
|
+
function trimExtName(text) {
|
|
1684
|
+
const dotIndex = text.lastIndexOf(".");
|
|
1685
|
+
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
1686
|
+
return text;
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Generates `index.ts` barrel files for all directories under `root/output.path`.
|
|
1690
|
+
*
|
|
1691
|
+
* - Returns an empty array when `type` is falsy or `'propagate'`.
|
|
1692
|
+
* - Skips generation when the output path itself ends with `index` (already a barrel).
|
|
1693
|
+
* - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
|
|
1694
|
+
* - Attaches `meta` to each barrel file for downstream plugin identification.
|
|
1695
|
+
*/
|
|
1696
|
+
async function getBarrelFiles(files, { type, meta = {}, root, output }) {
|
|
1697
|
+
if (!type || type === "propagate") return [];
|
|
1698
|
+
const pathToBuildFrom = join(root, output.path);
|
|
1699
|
+
if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
|
|
1700
|
+
const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
|
|
1701
|
+
if (type === "all") return barrelFiles.map((file) => {
|
|
1702
|
+
return {
|
|
1703
|
+
...file,
|
|
1704
|
+
exports: file.exports?.map((exportItem) => {
|
|
1705
|
+
return {
|
|
1706
|
+
...exportItem,
|
|
1707
|
+
name: void 0
|
|
1708
|
+
};
|
|
1709
|
+
})
|
|
1710
|
+
};
|
|
1711
|
+
});
|
|
1712
|
+
return barrelFiles.map((indexFile) => {
|
|
1713
|
+
return {
|
|
1714
|
+
...indexFile,
|
|
1715
|
+
meta
|
|
1716
|
+
};
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
//#endregion
|
|
1475
1720
|
//#region src/build.ts
|
|
1721
|
+
/**
|
|
1722
|
+
* Initializes all Kubb infrastructure for a build without executing any plugins.
|
|
1723
|
+
*
|
|
1724
|
+
* - Validates the input path (when applicable).
|
|
1725
|
+
* - Applies config defaults (`root`, `output.*`, `devtools`).
|
|
1726
|
+
* - Creates the Fabric instance and wires storage, format, and lint hooks.
|
|
1727
|
+
* - Runs the adapter (if configured) to produce the universal `RootNode`.
|
|
1728
|
+
*
|
|
1729
|
+
* Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
|
|
1730
|
+
* via the `overrides` argument to reuse the same infrastructure across multiple runs.
|
|
1731
|
+
*/
|
|
1476
1732
|
async function setup(options) {
|
|
1477
1733
|
const { config: userConfig, events = new AsyncEventEmitter() } = options;
|
|
1478
1734
|
const sources = /* @__PURE__ */ new Map();
|
|
@@ -1570,7 +1826,7 @@ async function setup(options) {
|
|
|
1570
1826
|
` • Barrel type: ${definedConfig.output.barrelType || "none"}`
|
|
1571
1827
|
]
|
|
1572
1828
|
});
|
|
1573
|
-
const
|
|
1829
|
+
const pluginDriver = new PluginDriver(definedConfig, {
|
|
1574
1830
|
fabric,
|
|
1575
1831
|
events,
|
|
1576
1832
|
concurrency: 15
|
|
@@ -1581,26 +1837,32 @@ async function setup(options) {
|
|
|
1581
1837
|
date: /* @__PURE__ */ new Date(),
|
|
1582
1838
|
logs: [`Running adapter: ${definedConfig.adapter.name}`]
|
|
1583
1839
|
});
|
|
1584
|
-
|
|
1585
|
-
|
|
1840
|
+
pluginDriver.adapter = definedConfig.adapter;
|
|
1841
|
+
pluginDriver.rootNode = await definedConfig.adapter.parse(source);
|
|
1586
1842
|
await events.emit("debug", {
|
|
1587
1843
|
date: /* @__PURE__ */ new Date(),
|
|
1588
1844
|
logs: [
|
|
1589
1845
|
`✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
|
|
1590
|
-
` • Schemas: ${
|
|
1591
|
-
` • Operations: ${
|
|
1846
|
+
` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
|
|
1847
|
+
` • Operations: ${pluginDriver.rootNode.operations.length}`
|
|
1592
1848
|
]
|
|
1593
1849
|
});
|
|
1594
1850
|
}
|
|
1595
1851
|
return {
|
|
1596
1852
|
events,
|
|
1597
1853
|
fabric,
|
|
1598
|
-
|
|
1854
|
+
driver: pluginDriver,
|
|
1599
1855
|
sources
|
|
1600
1856
|
};
|
|
1601
1857
|
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Runs a full Kubb build and throws on any error or plugin failure.
|
|
1860
|
+
*
|
|
1861
|
+
* Internally delegates to {@link safeBuild} and rethrows collected errors.
|
|
1862
|
+
* Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
|
|
1863
|
+
*/
|
|
1602
1864
|
async function build(options, overrides) {
|
|
1603
|
-
const { fabric, files,
|
|
1865
|
+
const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
|
|
1604
1866
|
if (error) throw error;
|
|
1605
1867
|
if (failedPlugins.size > 0) {
|
|
1606
1868
|
const errors = [...failedPlugins].map(({ error }) => error);
|
|
@@ -1610,30 +1872,96 @@ async function build(options, overrides) {
|
|
|
1610
1872
|
failedPlugins,
|
|
1611
1873
|
fabric,
|
|
1612
1874
|
files,
|
|
1613
|
-
|
|
1875
|
+
driver,
|
|
1614
1876
|
pluginTimings,
|
|
1615
1877
|
error: void 0,
|
|
1616
1878
|
sources
|
|
1617
1879
|
};
|
|
1618
1880
|
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
1883
|
+
* (`schema`, `operation`, `operations`).
|
|
1884
|
+
*
|
|
1885
|
+
* - Each hook accepts a single handler **or an array** — all entries are called in sequence.
|
|
1886
|
+
* - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
|
|
1887
|
+
* - Return values are handled via `applyHookResult`: React elements are rendered,
|
|
1888
|
+
* `FabricFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
|
|
1889
|
+
* - Barrel files are generated automatically when `output.barrelType` is set.
|
|
1890
|
+
*/
|
|
1891
|
+
async function runPluginAstHooks(plugin, context) {
|
|
1892
|
+
const { adapter, rootNode, resolver, fabric } = context;
|
|
1893
|
+
const { exclude, include, override } = plugin.options;
|
|
1894
|
+
if (!adapter || !rootNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
|
|
1895
|
+
const collectedOperations = [];
|
|
1896
|
+
await walk(rootNode, {
|
|
1897
|
+
depth: "shallow",
|
|
1898
|
+
async schema(node) {
|
|
1899
|
+
if (!plugin.schema) return;
|
|
1900
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
|
|
1901
|
+
const options = resolver.resolveOptions(transformedNode, {
|
|
1902
|
+
options: plugin.options,
|
|
1903
|
+
exclude,
|
|
1904
|
+
include,
|
|
1905
|
+
override
|
|
1906
|
+
});
|
|
1907
|
+
if (options === null) return;
|
|
1908
|
+
await applyHookResult(await plugin.schema.call(context, transformedNode, options), fabric);
|
|
1909
|
+
},
|
|
1910
|
+
async operation(node) {
|
|
1911
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
|
|
1912
|
+
const options = resolver.resolveOptions(transformedNode, {
|
|
1913
|
+
options: plugin.options,
|
|
1914
|
+
exclude,
|
|
1915
|
+
include,
|
|
1916
|
+
override
|
|
1917
|
+
});
|
|
1918
|
+
if (options !== null) {
|
|
1919
|
+
collectedOperations.push(transformedNode);
|
|
1920
|
+
if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), fabric);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), fabric);
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Runs a full Kubb build and captures errors instead of throwing.
|
|
1928
|
+
*
|
|
1929
|
+
* - Installs each plugin in order, recording failures in `failedPlugins`.
|
|
1930
|
+
* - Generates the root barrel file when `output.barrelType` is set.
|
|
1931
|
+
* - Writes all files through Fabric.
|
|
1932
|
+
*
|
|
1933
|
+
* Returns a {@link BuildOutput} even on failure — inspect `error` and
|
|
1934
|
+
* `failedPlugins` to determine whether the build succeeded.
|
|
1935
|
+
*/
|
|
1619
1936
|
async function safeBuild(options, overrides) {
|
|
1620
|
-
const { fabric,
|
|
1937
|
+
const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
|
|
1621
1938
|
const failedPlugins = /* @__PURE__ */ new Set();
|
|
1622
1939
|
const pluginTimings = /* @__PURE__ */ new Map();
|
|
1623
|
-
const config =
|
|
1940
|
+
const config = driver.config;
|
|
1624
1941
|
try {
|
|
1625
|
-
for (const plugin of
|
|
1626
|
-
const context =
|
|
1942
|
+
for (const plugin of driver.plugins.values()) {
|
|
1943
|
+
const context = driver.getContext(plugin);
|
|
1627
1944
|
const hrStart = process.hrtime();
|
|
1628
|
-
const
|
|
1945
|
+
const { output } = plugin.options ?? {};
|
|
1946
|
+
const root = resolve(config.root, config.output.path);
|
|
1629
1947
|
try {
|
|
1630
1948
|
const timestamp = /* @__PURE__ */ new Date();
|
|
1631
1949
|
await events.emit("plugin:start", plugin);
|
|
1632
1950
|
await events.emit("debug", {
|
|
1633
1951
|
date: timestamp,
|
|
1634
|
-
logs: ["
|
|
1952
|
+
logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
|
|
1635
1953
|
});
|
|
1636
|
-
await
|
|
1954
|
+
await plugin.buildStart.call(context);
|
|
1955
|
+
if (plugin.schema || plugin.operation || plugin.operations) await runPluginAstHooks(plugin, context);
|
|
1956
|
+
if (output) {
|
|
1957
|
+
const barrelFiles = await getBarrelFiles(fabric.files, {
|
|
1958
|
+
type: output.barrelType ?? "named",
|
|
1959
|
+
root,
|
|
1960
|
+
output,
|
|
1961
|
+
meta: { pluginName: plugin.name }
|
|
1962
|
+
});
|
|
1963
|
+
await context.upsertFile(...barrelFiles);
|
|
1964
|
+
}
|
|
1637
1965
|
const duration = getElapsedMs(hrStart);
|
|
1638
1966
|
pluginTimings.set(plugin.name, duration);
|
|
1639
1967
|
await events.emit("plugin:end", plugin, {
|
|
@@ -1642,7 +1970,7 @@ async function safeBuild(options, overrides) {
|
|
|
1642
1970
|
});
|
|
1643
1971
|
await events.emit("debug", {
|
|
1644
1972
|
date: /* @__PURE__ */ new Date(),
|
|
1645
|
-
logs: [`✓ Plugin
|
|
1973
|
+
logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
|
|
1646
1974
|
});
|
|
1647
1975
|
} catch (caughtError) {
|
|
1648
1976
|
const error = caughtError;
|
|
@@ -1656,7 +1984,7 @@ async function safeBuild(options, overrides) {
|
|
|
1656
1984
|
await events.emit("debug", {
|
|
1657
1985
|
date: errorTimestamp,
|
|
1658
1986
|
logs: [
|
|
1659
|
-
"✗ Plugin
|
|
1987
|
+
"✗ Plugin start failed",
|
|
1660
1988
|
` • Plugin Name: ${plugin.name}`,
|
|
1661
1989
|
` • Error: ${error.constructor.name} - ${error.message}`,
|
|
1662
1990
|
" • Stack Trace:",
|
|
@@ -1696,7 +2024,7 @@ async function safeBuild(options, overrides) {
|
|
|
1696
2024
|
rootDir,
|
|
1697
2025
|
existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
|
|
1698
2026
|
config,
|
|
1699
|
-
|
|
2027
|
+
driver
|
|
1700
2028
|
}),
|
|
1701
2029
|
sources: [],
|
|
1702
2030
|
imports: [],
|
|
@@ -1710,11 +2038,15 @@ async function safeBuild(options, overrides) {
|
|
|
1710
2038
|
}
|
|
1711
2039
|
const files = [...fabric.files];
|
|
1712
2040
|
await fabric.write({ extension: config.output.extension });
|
|
2041
|
+
for (const plugin of driver.plugins.values()) if (plugin.buildEnd) {
|
|
2042
|
+
const context = driver.getContext(plugin);
|
|
2043
|
+
await plugin.buildEnd.call(context);
|
|
2044
|
+
}
|
|
1713
2045
|
return {
|
|
1714
2046
|
failedPlugins,
|
|
1715
2047
|
fabric,
|
|
1716
2048
|
files,
|
|
1717
|
-
|
|
2049
|
+
driver,
|
|
1718
2050
|
pluginTimings,
|
|
1719
2051
|
sources
|
|
1720
2052
|
};
|
|
@@ -1723,16 +2055,16 @@ async function safeBuild(options, overrides) {
|
|
|
1723
2055
|
failedPlugins,
|
|
1724
2056
|
fabric,
|
|
1725
2057
|
files: [],
|
|
1726
|
-
|
|
2058
|
+
driver,
|
|
1727
2059
|
pluginTimings,
|
|
1728
2060
|
error,
|
|
1729
2061
|
sources
|
|
1730
2062
|
};
|
|
1731
2063
|
}
|
|
1732
2064
|
}
|
|
1733
|
-
function buildBarrelExports({ barrelFiles, rootDir, existingExports, config,
|
|
2065
|
+
function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }) {
|
|
1734
2066
|
const pluginNameMap = /* @__PURE__ */ new Map();
|
|
1735
|
-
for (const plugin of
|
|
2067
|
+
for (const plugin of driver.plugins.values()) pluginNameMap.set(plugin.name, plugin);
|
|
1736
2068
|
return barrelFiles.flatMap((file) => {
|
|
1737
2069
|
const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly);
|
|
1738
2070
|
return (file.sources ?? []).flatMap((source) => {
|
|
@@ -1757,164 +2089,520 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
|
|
|
1757
2089
|
function inputToAdapterSource(config) {
|
|
1758
2090
|
if (Array.isArray(config.input)) return {
|
|
1759
2091
|
type: "paths",
|
|
1760
|
-
paths: config.input.map((i) => resolve(config.root, i.path))
|
|
2092
|
+
paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))
|
|
1761
2093
|
};
|
|
1762
2094
|
if ("data" in config.input) return {
|
|
1763
2095
|
type: "data",
|
|
1764
2096
|
data: config.input.data
|
|
1765
2097
|
};
|
|
2098
|
+
if (new URLPath(config.input.path).isURL) return {
|
|
2099
|
+
type: "path",
|
|
2100
|
+
path: config.input.path
|
|
2101
|
+
};
|
|
1766
2102
|
return {
|
|
1767
2103
|
type: "path",
|
|
1768
2104
|
path: resolve(config.root, config.input.path)
|
|
1769
2105
|
};
|
|
1770
2106
|
}
|
|
1771
2107
|
//#endregion
|
|
1772
|
-
//#region src/
|
|
2108
|
+
//#region src/createAdapter.ts
|
|
1773
2109
|
/**
|
|
1774
|
-
*
|
|
2110
|
+
* Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
|
|
1775
2111
|
*
|
|
1776
2112
|
* @example
|
|
1777
|
-
*
|
|
1778
|
-
* export const adapterOas = defineAdapter<OasAdapter>((options) => {
|
|
1779
|
-
* const { validate = true, dateType = 'string' } = options
|
|
2113
|
+
* export const myAdapter = createAdapter<MyAdapter>((options) => {
|
|
1780
2114
|
* return {
|
|
1781
|
-
* name:
|
|
1782
|
-
* options
|
|
1783
|
-
* parse(source) { ... },
|
|
2115
|
+
* name: 'my-adapter',
|
|
2116
|
+
* options,
|
|
2117
|
+
* async parse(source) { ... },
|
|
1784
2118
|
* }
|
|
1785
2119
|
* })
|
|
1786
|
-
*
|
|
2120
|
+
*
|
|
2121
|
+
* // instantiate
|
|
2122
|
+
* const adapter = myAdapter({ validate: true })
|
|
1787
2123
|
*/
|
|
1788
|
-
function
|
|
2124
|
+
function createAdapter(build) {
|
|
1789
2125
|
return (options) => build(options ?? {});
|
|
1790
2126
|
}
|
|
1791
2127
|
//#endregion
|
|
1792
|
-
//#region src/
|
|
1793
|
-
function defineLogger(logger) {
|
|
1794
|
-
return { ...logger };
|
|
1795
|
-
}
|
|
1796
|
-
//#endregion
|
|
1797
|
-
//#region src/definePlugin.ts
|
|
2128
|
+
//#region src/createPlugin.ts
|
|
1798
2129
|
/**
|
|
1799
|
-
*
|
|
2130
|
+
* Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
|
|
2131
|
+
*
|
|
2132
|
+
* @example
|
|
2133
|
+
* ```ts
|
|
2134
|
+
* export const myPlugin = createPlugin<MyPlugin>((options) => {
|
|
2135
|
+
* return {
|
|
2136
|
+
* name: 'my-plugin',
|
|
2137
|
+
* get options() { return options },
|
|
2138
|
+
* resolvePath(baseName) { ... },
|
|
2139
|
+
* resolveName(name, type) { ... },
|
|
2140
|
+
* }
|
|
2141
|
+
* })
|
|
2142
|
+
*
|
|
2143
|
+
* // instantiate
|
|
2144
|
+
* const plugin = myPlugin({ output: { path: 'src/gen' } })
|
|
2145
|
+
* ```
|
|
1800
2146
|
*/
|
|
1801
|
-
function
|
|
2147
|
+
function createPlugin(build) {
|
|
1802
2148
|
return (options) => build(options ?? {});
|
|
1803
2149
|
}
|
|
1804
2150
|
//#endregion
|
|
1805
|
-
//#region src/
|
|
1806
|
-
var PackageManager = class PackageManager {
|
|
1807
|
-
static #cache = {};
|
|
1808
|
-
#cwd;
|
|
1809
|
-
constructor(workspace) {
|
|
1810
|
-
if (workspace) this.#cwd = workspace;
|
|
1811
|
-
}
|
|
1812
|
-
set workspace(workspace) {
|
|
1813
|
-
this.#cwd = workspace;
|
|
1814
|
-
}
|
|
1815
|
-
get workspace() {
|
|
1816
|
-
return this.#cwd;
|
|
1817
|
-
}
|
|
1818
|
-
normalizeDirectory(directory) {
|
|
1819
|
-
const lastChar = directory[directory.length - 1];
|
|
1820
|
-
if (lastChar && !PATH_SEPARATORS.includes(lastChar)) return `${directory}/`;
|
|
1821
|
-
return directory;
|
|
1822
|
-
}
|
|
1823
|
-
getLocation(path) {
|
|
1824
|
-
let location = path;
|
|
1825
|
-
if (this.#cwd) location = mod.createRequire(this.normalizeDirectory(this.#cwd)).resolve(path);
|
|
1826
|
-
return location;
|
|
1827
|
-
}
|
|
1828
|
-
async import(path) {
|
|
1829
|
-
let location = this.getLocation(path);
|
|
1830
|
-
if (os.platform() === "win32") location = pathToFileURL(location).href;
|
|
1831
|
-
const module = await import(location);
|
|
1832
|
-
return module?.default ?? module;
|
|
1833
|
-
}
|
|
1834
|
-
async getPackageJSON() {
|
|
1835
|
-
const pkgPath = pkg.up({ cwd: this.#cwd });
|
|
1836
|
-
if (!pkgPath) return;
|
|
1837
|
-
const json = await read(pkgPath);
|
|
1838
|
-
return JSON.parse(json);
|
|
1839
|
-
}
|
|
1840
|
-
getPackageJSONSync() {
|
|
1841
|
-
const pkgPath = pkg.up({ cwd: this.#cwd });
|
|
1842
|
-
if (!pkgPath) return;
|
|
1843
|
-
const json = readSync(pkgPath);
|
|
1844
|
-
return JSON.parse(json);
|
|
1845
|
-
}
|
|
1846
|
-
static setVersion(dependency, version) {
|
|
1847
|
-
PackageManager.#cache[dependency] = version;
|
|
1848
|
-
}
|
|
1849
|
-
#match(packageJSON, dependency) {
|
|
1850
|
-
const dependencies = {
|
|
1851
|
-
...packageJSON.dependencies || {},
|
|
1852
|
-
...packageJSON.devDependencies || {}
|
|
1853
|
-
};
|
|
1854
|
-
if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
|
|
1855
|
-
const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency));
|
|
1856
|
-
return matchedDependency ? dependencies[matchedDependency] : void 0;
|
|
1857
|
-
}
|
|
1858
|
-
async getVersion(dependency) {
|
|
1859
|
-
if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
|
|
1860
|
-
const packageJSON = await this.getPackageJSON();
|
|
1861
|
-
if (!packageJSON) return;
|
|
1862
|
-
return this.#match(packageJSON, dependency);
|
|
1863
|
-
}
|
|
1864
|
-
getVersionSync(dependency) {
|
|
1865
|
-
if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
|
|
1866
|
-
const packageJSON = this.getPackageJSONSync();
|
|
1867
|
-
if (!packageJSON) return;
|
|
1868
|
-
return this.#match(packageJSON, dependency);
|
|
1869
|
-
}
|
|
1870
|
-
async isValid(dependency, version) {
|
|
1871
|
-
const packageVersion = await this.getVersion(dependency);
|
|
1872
|
-
if (!packageVersion) return false;
|
|
1873
|
-
if (packageVersion === version) return true;
|
|
1874
|
-
const semVer = coerce(packageVersion);
|
|
1875
|
-
if (!semVer) return false;
|
|
1876
|
-
return satisfies(semVer, version);
|
|
1877
|
-
}
|
|
1878
|
-
isValidSync(dependency, version) {
|
|
1879
|
-
const packageVersion = this.getVersionSync(dependency);
|
|
1880
|
-
if (!packageVersion) return false;
|
|
1881
|
-
if (packageVersion === version) return true;
|
|
1882
|
-
const semVer = coerce(packageVersion);
|
|
1883
|
-
if (!semVer) return false;
|
|
1884
|
-
return satisfies(semVer, version);
|
|
1885
|
-
}
|
|
1886
|
-
};
|
|
1887
|
-
//#endregion
|
|
1888
|
-
//#region src/storages/memoryStorage.ts
|
|
2151
|
+
//#region src/defineGenerator.ts
|
|
1889
2152
|
/**
|
|
1890
|
-
*
|
|
1891
|
-
*
|
|
2153
|
+
* Defines a generator. Returns the object as-is with correct `this` typings.
|
|
2154
|
+
* No type discrimination (`type: 'react' | 'core'`) needed — `applyHookResult`
|
|
2155
|
+
* handles React elements and `File[]` uniformly.
|
|
2156
|
+
*/
|
|
2157
|
+
function defineGenerator(generator) {
|
|
2158
|
+
return generator;
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Merges an array of generators into a single generator.
|
|
1892
2162
|
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
2163
|
+
* The merged generator's `schema`, `operation`, and `operations` methods run
|
|
2164
|
+
* the corresponding method from each input generator in sequence, applying each
|
|
2165
|
+
* result via `applyHookResult`. This eliminates the need to write the loop
|
|
2166
|
+
* manually in each plugin.
|
|
2167
|
+
*
|
|
2168
|
+
* @param generators - Array of generators to merge into a single generator.
|
|
1895
2169
|
*
|
|
1896
2170
|
* @example
|
|
1897
2171
|
* ```ts
|
|
1898
|
-
*
|
|
2172
|
+
* const merged = mergeGenerators(generators)
|
|
1899
2173
|
*
|
|
1900
|
-
*
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
1903
|
-
*
|
|
2174
|
+
* return {
|
|
2175
|
+
* name: pluginName,
|
|
2176
|
+
* schema: merged.schema,
|
|
2177
|
+
* operation: merged.operation,
|
|
2178
|
+
* operations: merged.operations,
|
|
2179
|
+
* }
|
|
1904
2180
|
* ```
|
|
1905
2181
|
*/
|
|
1906
|
-
|
|
1907
|
-
const store = /* @__PURE__ */ new Map();
|
|
2182
|
+
function mergeGenerators(generators) {
|
|
1908
2183
|
return {
|
|
1909
|
-
name: "
|
|
1910
|
-
async
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2184
|
+
name: generators.length > 0 ? generators.map((g) => g.name).join("+") : "merged",
|
|
2185
|
+
async schema(node, options) {
|
|
2186
|
+
for (const gen of generators) {
|
|
2187
|
+
if (!gen.schema) continue;
|
|
2188
|
+
await applyHookResult(await gen.schema.call(this, node, options), this.fabric);
|
|
2189
|
+
}
|
|
1915
2190
|
},
|
|
1916
|
-
async
|
|
1917
|
-
|
|
2191
|
+
async operation(node, options) {
|
|
2192
|
+
for (const gen of generators) {
|
|
2193
|
+
if (!gen.operation) continue;
|
|
2194
|
+
await applyHookResult(await gen.operation.call(this, node, options), this.fabric);
|
|
2195
|
+
}
|
|
2196
|
+
},
|
|
2197
|
+
async operations(nodes, options) {
|
|
2198
|
+
for (const gen of generators) {
|
|
2199
|
+
if (!gen.operations) continue;
|
|
2200
|
+
await applyHookResult(await gen.operations.call(this, nodes, options), this.fabric);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
//#endregion
|
|
2206
|
+
//#region src/defineLogger.ts
|
|
2207
|
+
/**
|
|
2208
|
+
* Wraps a logger definition into a typed {@link Logger}.
|
|
2209
|
+
*
|
|
2210
|
+
* @example
|
|
2211
|
+
* export const myLogger = defineLogger({
|
|
2212
|
+
* name: 'my-logger',
|
|
2213
|
+
* install(context, options) {
|
|
2214
|
+
* context.on('info', (message) => console.log('ℹ', message))
|
|
2215
|
+
* context.on('error', (error) => console.error('✗', error.message))
|
|
2216
|
+
* },
|
|
2217
|
+
* })
|
|
2218
|
+
*/
|
|
2219
|
+
function defineLogger(logger) {
|
|
2220
|
+
return logger;
|
|
2221
|
+
}
|
|
2222
|
+
//#endregion
|
|
2223
|
+
//#region src/definePresets.ts
|
|
2224
|
+
/**
|
|
2225
|
+
* Creates a typed presets registry object — a named collection of {@link Preset} entries.
|
|
2226
|
+
*
|
|
2227
|
+
* @example
|
|
2228
|
+
* import { definePreset, definePresets } from '@kubb/core'
|
|
2229
|
+
* import { resolverTsLegacy } from '@kubb/plugin-ts'
|
|
2230
|
+
*
|
|
2231
|
+
* export const myPresets = definePresets({
|
|
2232
|
+
* kubbV4: definePreset('kubbV4', { resolvers: [resolverTsLegacy] }),
|
|
2233
|
+
* })
|
|
2234
|
+
*/
|
|
2235
|
+
function definePresets(presets) {
|
|
2236
|
+
return presets;
|
|
2237
|
+
}
|
|
2238
|
+
//#endregion
|
|
2239
|
+
//#region src/defineResolver.ts
|
|
2240
|
+
/**
|
|
2241
|
+
* Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
|
|
2242
|
+
*/
|
|
2243
|
+
function matchesOperationPattern(node, type, pattern) {
|
|
2244
|
+
switch (type) {
|
|
2245
|
+
case "tag": return node.tags.some((tag) => !!tag.match(pattern));
|
|
2246
|
+
case "operationId": return !!node.operationId.match(pattern);
|
|
2247
|
+
case "path": return !!node.path.match(pattern);
|
|
2248
|
+
case "method": return !!node.method.toLowerCase().match(pattern);
|
|
2249
|
+
case "contentType": return !!node.requestBody?.contentType?.match(pattern);
|
|
2250
|
+
default: return false;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Checks if a schema matches a pattern for a given filter type (`schemaName`).
|
|
2255
|
+
*
|
|
2256
|
+
* Returns `null` when the filter type doesn't apply to schemas.
|
|
2257
|
+
*/
|
|
2258
|
+
function matchesSchemaPattern(node, type, pattern) {
|
|
2259
|
+
switch (type) {
|
|
2260
|
+
case "schemaName": return node.name ? !!node.name.match(pattern) : false;
|
|
2261
|
+
default: return null;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Default name resolver used by `defineResolver`.
|
|
2266
|
+
*
|
|
2267
|
+
* - `camelCase` for `function` and `file` types.
|
|
2268
|
+
* - `PascalCase` for `type`.
|
|
2269
|
+
* - `camelCase` for everything else.
|
|
2270
|
+
*/
|
|
2271
|
+
function defaultResolver(name, type) {
|
|
2272
|
+
let resolvedName = camelCase(name);
|
|
2273
|
+
if (type === "file" || type === "function") resolvedName = camelCase(name, { isFile: type === "file" });
|
|
2274
|
+
if (type === "type") resolvedName = pascalCase(name);
|
|
2275
|
+
return resolvedName;
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Default option resolver — applies include/exclude filters and merges matching override options.
|
|
2279
|
+
*
|
|
2280
|
+
* Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
|
|
2281
|
+
*
|
|
2282
|
+
* @example Include/exclude filtering
|
|
2283
|
+
* ```ts
|
|
2284
|
+
* const options = defaultResolveOptions(operationNode, {
|
|
2285
|
+
* options: { output: 'types' },
|
|
2286
|
+
* exclude: [{ type: 'tag', pattern: 'internal' }],
|
|
2287
|
+
* })
|
|
2288
|
+
* // → null when node has tag 'internal'
|
|
2289
|
+
* ```
|
|
2290
|
+
*
|
|
2291
|
+
* @example Override merging
|
|
2292
|
+
* ```ts
|
|
2293
|
+
* const options = defaultResolveOptions(operationNode, {
|
|
2294
|
+
* options: { enumType: 'asConst' },
|
|
2295
|
+
* override: [{ type: 'operationId', pattern: 'listPets', options: { enumType: 'enum' } }],
|
|
2296
|
+
* })
|
|
2297
|
+
* // → { enumType: 'enum' } when operationId matches
|
|
2298
|
+
* ```
|
|
2299
|
+
*/
|
|
2300
|
+
function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
|
|
2301
|
+
if (isOperationNode(node)) {
|
|
2302
|
+
if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
2303
|
+
if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
2304
|
+
const overrideOptions = override.find(({ type, pattern }) => matchesOperationPattern(node, type, pattern))?.options;
|
|
2305
|
+
return {
|
|
2306
|
+
...options,
|
|
2307
|
+
...overrideOptions
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
if (isSchemaNode(node)) {
|
|
2311
|
+
if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
|
|
2312
|
+
if (include) {
|
|
2313
|
+
const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((r) => r !== null);
|
|
2314
|
+
if (applicable.length > 0 && !applicable.includes(true)) return null;
|
|
2315
|
+
}
|
|
2316
|
+
const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
|
|
2317
|
+
return {
|
|
2318
|
+
...options,
|
|
2319
|
+
...overrideOptions
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
return options;
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Default path resolver used by `defineResolver`.
|
|
2326
|
+
*
|
|
2327
|
+
* - Returns the output directory in `single` mode.
|
|
2328
|
+
* - Resolves into a tag- or path-based subdirectory when `group` and a `tag`/`path` value are provided.
|
|
2329
|
+
* - Falls back to a flat `output/baseName` path otherwise.
|
|
2330
|
+
*
|
|
2331
|
+
* A custom `group.name` function overrides the default subdirectory naming.
|
|
2332
|
+
* For `tag` groups the default is `${camelCase(tag)}Controller`.
|
|
2333
|
+
* For `path` groups the default is the first path segment after `/`.
|
|
2334
|
+
*
|
|
2335
|
+
* @example Flat output
|
|
2336
|
+
* ```ts
|
|
2337
|
+
* defaultResolvePath({ baseName: 'petTypes.ts' }, { root: '/src', output: { path: 'types' } })
|
|
2338
|
+
* // → '/src/types/petTypes.ts'
|
|
2339
|
+
* ```
|
|
2340
|
+
*
|
|
2341
|
+
* @example Tag-based grouping
|
|
2342
|
+
* ```ts
|
|
2343
|
+
* defaultResolvePath(
|
|
2344
|
+
* { baseName: 'petTypes.ts', tag: 'pets' },
|
|
2345
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
2346
|
+
* )
|
|
2347
|
+
* // → '/src/types/petsController/petTypes.ts'
|
|
2348
|
+
* ```
|
|
2349
|
+
*
|
|
2350
|
+
* @example Path-based grouping
|
|
2351
|
+
* ```ts
|
|
2352
|
+
* defaultResolvePath(
|
|
2353
|
+
* { baseName: 'petTypes.ts', path: '/pets/list' },
|
|
2354
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'path' } },
|
|
2355
|
+
* )
|
|
2356
|
+
* // → '/src/types/pets/petTypes.ts'
|
|
2357
|
+
* ```
|
|
2358
|
+
*
|
|
2359
|
+
* @example Single-file mode
|
|
2360
|
+
* ```ts
|
|
2361
|
+
* defaultResolvePath(
|
|
2362
|
+
* { baseName: 'petTypes.ts', pathMode: 'single' },
|
|
2363
|
+
* { root: '/src', output: { path: 'types' } },
|
|
2364
|
+
* )
|
|
2365
|
+
* // → '/src/types'
|
|
2366
|
+
* ```
|
|
2367
|
+
*/
|
|
2368
|
+
function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root, output, group }) {
|
|
2369
|
+
if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single") return path.resolve(root, output.path);
|
|
2370
|
+
if (group && (groupPath || tag)) return path.resolve(root, output.path, group.name({ group: group.type === "path" ? groupPath : tag }), baseName);
|
|
2371
|
+
return path.resolve(root, output.path, baseName);
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* Default file resolver used by `defineResolver`.
|
|
2375
|
+
*
|
|
2376
|
+
* Resolves a `FabricFile.File` by combining name resolution (`resolver.default`) with
|
|
2377
|
+
* path resolution (`resolver.resolvePath`). The resolved file always has empty
|
|
2378
|
+
* `sources`, `imports`, and `exports` arrays — consumers populate those separately.
|
|
2379
|
+
*
|
|
2380
|
+
* In `single` mode the name is omitted and the file sits directly in the output directory.
|
|
2381
|
+
*
|
|
2382
|
+
* @example Resolve a schema file
|
|
2383
|
+
* ```ts
|
|
2384
|
+
* const file = defaultResolveFile.call(resolver,
|
|
2385
|
+
* { name: 'pet', extname: '.ts' },
|
|
2386
|
+
* { root: '/src', output: { path: 'types' } },
|
|
2387
|
+
* )
|
|
2388
|
+
* // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
|
|
2389
|
+
* ```
|
|
2390
|
+
*
|
|
2391
|
+
* @example Resolve an operation file with tag grouping
|
|
2392
|
+
* ```ts
|
|
2393
|
+
* const file = defaultResolveFile.call(resolver,
|
|
2394
|
+
* { name: 'listPets', extname: '.ts', tag: 'pets' },
|
|
2395
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
2396
|
+
* )
|
|
2397
|
+
* // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
|
|
2398
|
+
* ```
|
|
2399
|
+
*/
|
|
2400
|
+
function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
|
|
2401
|
+
const pathMode = getMode(path.resolve(context.root, context.output.path));
|
|
2402
|
+
const baseName = `${pathMode === "single" ? "" : this.default(name, "file")}${extname}`;
|
|
2403
|
+
const filePath = this.resolvePath({
|
|
2404
|
+
baseName,
|
|
2405
|
+
pathMode,
|
|
2406
|
+
tag,
|
|
2407
|
+
path: groupPath
|
|
2408
|
+
}, context);
|
|
2409
|
+
return {
|
|
2410
|
+
path: filePath,
|
|
2411
|
+
baseName: path.basename(filePath),
|
|
2412
|
+
meta: { pluginName: this.pluginName },
|
|
2413
|
+
sources: [],
|
|
2414
|
+
imports: [],
|
|
2415
|
+
exports: []
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Generates the default "Generated by Kubb" banner from config and optional node metadata.
|
|
2420
|
+
*/
|
|
2421
|
+
function buildDefaultBanner({ title, description, version, config }) {
|
|
2422
|
+
try {
|
|
2423
|
+
let source = "";
|
|
2424
|
+
if (Array.isArray(config.input)) {
|
|
2425
|
+
const first = config.input[0];
|
|
2426
|
+
if (first && "path" in first) source = path.basename(first.path);
|
|
2427
|
+
} else if ("path" in config.input) source = path.basename(config.input.path);
|
|
2428
|
+
else if ("data" in config.input) source = "text content";
|
|
2429
|
+
let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
|
|
2430
|
+
if (config.output.defaultBanner === "simple") {
|
|
2431
|
+
banner += "*/\n";
|
|
2432
|
+
return banner;
|
|
2433
|
+
}
|
|
2434
|
+
if (source) banner += `* Source: ${source}\n`;
|
|
2435
|
+
if (title) banner += `* Title: ${title}\n`;
|
|
2436
|
+
if (description) {
|
|
2437
|
+
const formattedDescription = description.replace(/\n/gm, "\n* ");
|
|
2438
|
+
banner += `* Description: ${formattedDescription}\n`;
|
|
2439
|
+
}
|
|
2440
|
+
if (version) banner += `* OpenAPI spec version: ${version}\n`;
|
|
2441
|
+
banner += "*/\n";
|
|
2442
|
+
return banner;
|
|
2443
|
+
} catch (_error) {
|
|
2444
|
+
return "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n*/";
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Default banner resolver — returns the banner string for a generated file.
|
|
2449
|
+
*
|
|
2450
|
+
* A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
|
|
2451
|
+
* When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
|
|
2452
|
+
* from the OAS spec when a `node` is provided).
|
|
2453
|
+
*
|
|
2454
|
+
* - When `output.banner` is a function and `node` is provided, returns `output.banner(node)`.
|
|
2455
|
+
* - When `output.banner` is a function and `node` is absent, falls back to the Kubb notice.
|
|
2456
|
+
* - When `output.banner` is a string, returns it directly.
|
|
2457
|
+
* - When `config.output.defaultBanner` is `false`, returns `undefined`.
|
|
2458
|
+
* - Otherwise returns the Kubb "Generated by Kubb" notice.
|
|
2459
|
+
*
|
|
2460
|
+
* @example String banner overrides default
|
|
2461
|
+
* ```ts
|
|
2462
|
+
* defaultResolveBanner(undefined, { output: { banner: '// my banner' }, config })
|
|
2463
|
+
* // → '// my banner'
|
|
2464
|
+
* ```
|
|
2465
|
+
*
|
|
2466
|
+
* @example Function banner with node
|
|
2467
|
+
* ```ts
|
|
2468
|
+
* defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
|
|
2469
|
+
* // → '// v3.0.0'
|
|
2470
|
+
* ```
|
|
2471
|
+
*
|
|
2472
|
+
* @example No user banner — Kubb notice with OAS metadata
|
|
2473
|
+
* ```ts
|
|
2474
|
+
* defaultResolveBanner(rootNode, { config })
|
|
2475
|
+
* // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
|
|
2476
|
+
* ```
|
|
2477
|
+
*
|
|
2478
|
+
* @example Disabled default banner
|
|
2479
|
+
* ```ts
|
|
2480
|
+
* defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
|
|
2481
|
+
* // → undefined
|
|
2482
|
+
* ```
|
|
2483
|
+
*/
|
|
2484
|
+
function defaultResolveBanner(node, { output, config }) {
|
|
2485
|
+
if (typeof output?.banner === "function") return output.banner(node);
|
|
2486
|
+
if (typeof output?.banner === "string") return output.banner;
|
|
2487
|
+
if (config.output.defaultBanner === false) return;
|
|
2488
|
+
return buildDefaultBanner({
|
|
2489
|
+
title: node?.meta?.title,
|
|
2490
|
+
version: node?.meta?.version,
|
|
2491
|
+
config
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* Default footer resolver — returns the footer string for a generated file.
|
|
2496
|
+
*
|
|
2497
|
+
* - When `output.footer` is a function and `node` is provided, calls it with the node.
|
|
2498
|
+
* - When `output.footer` is a function and `node` is absent, returns `undefined`.
|
|
2499
|
+
* - When `output.footer` is a string, returns it directly.
|
|
2500
|
+
* - Otherwise returns `undefined`.
|
|
2501
|
+
*
|
|
2502
|
+
* @example String footer
|
|
2503
|
+
* ```ts
|
|
2504
|
+
* defaultResolveFooter(undefined, { output: { footer: '// end of file' }, config })
|
|
2505
|
+
* // → '// end of file'
|
|
2506
|
+
* ```
|
|
2507
|
+
*
|
|
2508
|
+
* @example Function footer with node
|
|
2509
|
+
* ```ts
|
|
2510
|
+
* defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
|
|
2511
|
+
* // → '// Pet Store'
|
|
2512
|
+
* ```
|
|
2513
|
+
*/
|
|
2514
|
+
function defaultResolveFooter(node, { output }) {
|
|
2515
|
+
if (typeof output?.footer === "function") return node ? output.footer(node) : void 0;
|
|
2516
|
+
if (typeof output?.footer === "string") return output.footer;
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* Defines a resolver for a plugin, injecting built-in defaults for name casing,
|
|
2520
|
+
* include/exclude/override filtering, path resolution, and file construction.
|
|
2521
|
+
*
|
|
2522
|
+
* All four defaults can be overridden by providing them in the builder function:
|
|
2523
|
+
* - `default` — name casing strategy (camelCase / PascalCase)
|
|
2524
|
+
* - `resolveOptions` — include/exclude/override filtering
|
|
2525
|
+
* - `resolvePath` — output path computation
|
|
2526
|
+
* - `resolveFile` — full `FabricFile.File` construction
|
|
2527
|
+
*
|
|
2528
|
+
* Methods in the builder have access to `this` (the full resolver object), so they
|
|
2529
|
+
* can call other resolver methods without circular imports.
|
|
2530
|
+
*
|
|
2531
|
+
* @example Basic resolver with naming helpers
|
|
2532
|
+
* ```ts
|
|
2533
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
2534
|
+
* name: 'default',
|
|
2535
|
+
* resolveName(node) {
|
|
2536
|
+
* return this.default(node.name, 'function')
|
|
2537
|
+
* },
|
|
2538
|
+
* resolveTypedName(node) {
|
|
2539
|
+
* return this.default(node.name, 'type')
|
|
2540
|
+
* },
|
|
2541
|
+
* }))
|
|
2542
|
+
* ```
|
|
2543
|
+
*
|
|
2544
|
+
* @example Override resolvePath for a custom output structure
|
|
2545
|
+
* ```ts
|
|
2546
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
2547
|
+
* name: 'custom',
|
|
2548
|
+
* resolvePath({ baseName }, { root, output }) {
|
|
2549
|
+
* return path.resolve(root, output.path, 'generated', baseName)
|
|
2550
|
+
* },
|
|
2551
|
+
* }))
|
|
2552
|
+
* ```
|
|
2553
|
+
*
|
|
2554
|
+
* @example Use this.default inside a helper
|
|
2555
|
+
* ```ts
|
|
2556
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
2557
|
+
* name: 'default',
|
|
2558
|
+
* resolveParamName(node, param) {
|
|
2559
|
+
* return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
|
|
2560
|
+
* },
|
|
2561
|
+
* }))
|
|
2562
|
+
* ```
|
|
2563
|
+
*/
|
|
2564
|
+
function defineResolver(build) {
|
|
2565
|
+
return {
|
|
2566
|
+
default: defaultResolver,
|
|
2567
|
+
resolveOptions: defaultResolveOptions,
|
|
2568
|
+
resolvePath: defaultResolvePath,
|
|
2569
|
+
resolveFile: defaultResolveFile,
|
|
2570
|
+
resolveBanner: defaultResolveBanner,
|
|
2571
|
+
resolveFooter: defaultResolveFooter,
|
|
2572
|
+
...build()
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
//#endregion
|
|
2576
|
+
//#region src/storages/memoryStorage.ts
|
|
2577
|
+
/**
|
|
2578
|
+
* In-memory storage driver. Useful for testing and dry-run scenarios where
|
|
2579
|
+
* generated output should be captured without touching the filesystem.
|
|
2580
|
+
*
|
|
2581
|
+
* All data lives in a `Map` scoped to the storage instance and is discarded
|
|
2582
|
+
* when the instance is garbage-collected.
|
|
2583
|
+
*
|
|
2584
|
+
* @example
|
|
2585
|
+
* ```ts
|
|
2586
|
+
* import { defineConfig, memoryStorage } from '@kubb/core'
|
|
2587
|
+
*
|
|
2588
|
+
* export default defineConfig({
|
|
2589
|
+
* input: { path: './petStore.yaml' },
|
|
2590
|
+
* output: { path: './src/gen', storage: memoryStorage() },
|
|
2591
|
+
* })
|
|
2592
|
+
* ```
|
|
2593
|
+
*/
|
|
2594
|
+
const memoryStorage = createStorage(() => {
|
|
2595
|
+
const store = /* @__PURE__ */ new Map();
|
|
2596
|
+
return {
|
|
2597
|
+
name: "memory",
|
|
2598
|
+
async hasItem(key) {
|
|
2599
|
+
return store.has(key);
|
|
2600
|
+
},
|
|
2601
|
+
async getItem(key) {
|
|
2602
|
+
return store.get(key) ?? null;
|
|
2603
|
+
},
|
|
2604
|
+
async setItem(key, value) {
|
|
2605
|
+
store.set(key, value);
|
|
1918
2606
|
},
|
|
1919
2607
|
async removeItem(key) {
|
|
1920
2608
|
store.delete(key);
|
|
@@ -1935,7 +2623,7 @@ const memoryStorage = defineStorage(() => {
|
|
|
1935
2623
|
//#endregion
|
|
1936
2624
|
//#region src/utils/FunctionParams.ts
|
|
1937
2625
|
/**
|
|
1938
|
-
* @deprecated
|
|
2626
|
+
* @deprecated use ast package instead
|
|
1939
2627
|
*/
|
|
1940
2628
|
var FunctionParams = class FunctionParams {
|
|
1941
2629
|
#items = [];
|
|
@@ -2011,14 +2699,10 @@ var FunctionParams = class FunctionParams {
|
|
|
2011
2699
|
//#endregion
|
|
2012
2700
|
//#region src/utils/formatters.ts
|
|
2013
2701
|
/**
|
|
2014
|
-
*
|
|
2702
|
+
* Returns `true` when the given formatter is installed and callable.
|
|
2015
2703
|
*
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
2018
|
-
*
|
|
2019
|
-
* @remarks
|
|
2020
|
-
* This function checks availability by running `<formatter> --version` command.
|
|
2021
|
-
* All supported formatters (biome, prettier, oxfmt) implement the --version flag.
|
|
2704
|
+
* Availability is detected by running `<formatter> --version` and checking
|
|
2705
|
+
* that the process exits without error.
|
|
2022
2706
|
*/
|
|
2023
2707
|
async function isFormatterAvailable(formatter) {
|
|
2024
2708
|
try {
|
|
@@ -2029,263 +2713,93 @@ async function isFormatterAvailable(formatter) {
|
|
|
2029
2713
|
}
|
|
2030
2714
|
}
|
|
2031
2715
|
/**
|
|
2032
|
-
*
|
|
2033
|
-
*
|
|
2034
|
-
* @returns Promise that resolves to the first available formatter or undefined if none are found
|
|
2716
|
+
* Detects the first available code formatter on the current system.
|
|
2035
2717
|
*
|
|
2036
|
-
*
|
|
2037
|
-
*
|
|
2038
|
-
* Uses the `--version` flag to detect if each formatter command is available.
|
|
2039
|
-
* This is a reliable method as all supported formatters implement this flag.
|
|
2718
|
+
* - Checks in preference order: `biome`, `oxfmt`, `prettier`.
|
|
2719
|
+
* - Returns `null` when none are found.
|
|
2040
2720
|
*
|
|
2041
2721
|
* @example
|
|
2042
|
-
* ```
|
|
2722
|
+
* ```ts
|
|
2043
2723
|
* const formatter = await detectFormatter()
|
|
2044
2724
|
* if (formatter) {
|
|
2045
2725
|
* console.log(`Using ${formatter} for formatting`)
|
|
2046
|
-
* } else {
|
|
2047
|
-
* console.log('No formatter found')
|
|
2048
2726
|
* }
|
|
2049
2727
|
* ```
|
|
2050
2728
|
*/
|
|
2051
2729
|
async function detectFormatter() {
|
|
2052
|
-
|
|
2730
|
+
const formatterNames = new Set([
|
|
2053
2731
|
"biome",
|
|
2054
2732
|
"oxfmt",
|
|
2055
2733
|
"prettier"
|
|
2056
|
-
])
|
|
2734
|
+
]);
|
|
2735
|
+
for (const formatter of formatterNames) if (await isFormatterAvailable(formatter)) return formatter;
|
|
2736
|
+
return null;
|
|
2057
2737
|
}
|
|
2058
2738
|
//#endregion
|
|
2059
|
-
//#region src/utils/
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
return child;
|
|
2074
|
-
}
|
|
2075
|
-
get root() {
|
|
2076
|
-
if (!this.parent) return this;
|
|
2077
|
-
return this.parent.root;
|
|
2078
|
-
}
|
|
2079
|
-
get leaves() {
|
|
2080
|
-
if (!this.children || this.children.length === 0) return [this];
|
|
2081
|
-
if (this.#cachedLeaves) return this.#cachedLeaves;
|
|
2082
|
-
const leaves = [];
|
|
2083
|
-
for (const child of this.children) leaves.push(...child.leaves);
|
|
2084
|
-
this.#cachedLeaves = leaves;
|
|
2085
|
-
return leaves;
|
|
2086
|
-
}
|
|
2087
|
-
forEach(callback) {
|
|
2088
|
-
if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
|
|
2089
|
-
callback(this);
|
|
2090
|
-
for (const child of this.children) child.forEach(callback);
|
|
2091
|
-
return this;
|
|
2092
|
-
}
|
|
2093
|
-
findDeep(predicate) {
|
|
2094
|
-
if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
|
|
2095
|
-
return this.leaves.find(predicate);
|
|
2096
|
-
}
|
|
2097
|
-
forEachDeep(callback) {
|
|
2098
|
-
if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
|
|
2099
|
-
this.leaves.forEach(callback);
|
|
2100
|
-
}
|
|
2101
|
-
filterDeep(callback) {
|
|
2102
|
-
if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
|
|
2103
|
-
return this.leaves.filter(callback);
|
|
2104
|
-
}
|
|
2105
|
-
mapDeep(callback) {
|
|
2106
|
-
if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
|
|
2107
|
-
return this.leaves.map(callback);
|
|
2108
|
-
}
|
|
2109
|
-
static build(files, root) {
|
|
2110
|
-
try {
|
|
2111
|
-
const filteredTree = buildDirectoryTree(files, root);
|
|
2112
|
-
if (!filteredTree) return null;
|
|
2113
|
-
const treeNode = new TreeNode({
|
|
2114
|
-
name: filteredTree.name,
|
|
2115
|
-
path: filteredTree.path,
|
|
2116
|
-
file: filteredTree.file,
|
|
2117
|
-
type: getMode(filteredTree.path)
|
|
2118
|
-
});
|
|
2119
|
-
const recurse = (node, item) => {
|
|
2120
|
-
const subNode = node.addChild({
|
|
2121
|
-
name: item.name,
|
|
2122
|
-
path: item.path,
|
|
2123
|
-
file: item.file,
|
|
2124
|
-
type: getMode(item.path)
|
|
2125
|
-
});
|
|
2126
|
-
if (item.children?.length) item.children?.forEach((child) => {
|
|
2127
|
-
recurse(subNode, child);
|
|
2128
|
-
});
|
|
2129
|
-
};
|
|
2130
|
-
filteredTree.children?.forEach((child) => {
|
|
2131
|
-
recurse(treeNode, child);
|
|
2132
|
-
});
|
|
2133
|
-
return treeNode;
|
|
2134
|
-
} catch (error) {
|
|
2135
|
-
throw new Error("Something went wrong with creating barrel files with the TreeNode class", { cause: error });
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
};
|
|
2139
|
-
const normalizePath = (p) => p.replaceAll("\\", "/");
|
|
2140
|
-
function buildDirectoryTree(files, rootFolder = "") {
|
|
2141
|
-
const normalizedRootFolder = normalizePath(rootFolder);
|
|
2142
|
-
const rootPrefix = normalizedRootFolder.endsWith("/") ? normalizedRootFolder : `${normalizedRootFolder}/`;
|
|
2143
|
-
const filteredFiles = files.filter((file) => {
|
|
2144
|
-
const normalizedFilePath = normalizePath(file.path);
|
|
2145
|
-
return rootFolder ? normalizedFilePath.startsWith(rootPrefix) && !normalizedFilePath.endsWith(".json") : !normalizedFilePath.endsWith(".json");
|
|
2146
|
-
});
|
|
2147
|
-
if (filteredFiles.length === 0) return null;
|
|
2148
|
-
const root = {
|
|
2149
|
-
name: rootFolder || "",
|
|
2150
|
-
path: rootFolder || "",
|
|
2151
|
-
children: []
|
|
2152
|
-
};
|
|
2153
|
-
filteredFiles.forEach((file) => {
|
|
2154
|
-
const parts = file.path.slice(rootFolder.length).split("/").filter(Boolean);
|
|
2155
|
-
let currentLevel = root.children;
|
|
2156
|
-
let currentPath = normalizePath(rootFolder);
|
|
2157
|
-
parts.forEach((part, index) => {
|
|
2158
|
-
currentPath = path.posix.join(currentPath, part);
|
|
2159
|
-
let existingNode = currentLevel.find((node) => node.name === part);
|
|
2160
|
-
if (!existingNode) {
|
|
2161
|
-
if (index === parts.length - 1) existingNode = {
|
|
2162
|
-
name: part,
|
|
2163
|
-
file,
|
|
2164
|
-
path: currentPath
|
|
2165
|
-
};
|
|
2166
|
-
else existingNode = {
|
|
2167
|
-
name: part,
|
|
2168
|
-
path: currentPath,
|
|
2169
|
-
children: []
|
|
2170
|
-
};
|
|
2171
|
-
currentLevel.push(existingNode);
|
|
2172
|
-
}
|
|
2173
|
-
if (!existingNode.file) currentLevel = existingNode.children;
|
|
2174
|
-
});
|
|
2175
|
-
});
|
|
2176
|
-
return root;
|
|
2177
|
-
}
|
|
2178
|
-
//#endregion
|
|
2179
|
-
//#region src/BarrelManager.ts
|
|
2180
|
-
/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
|
|
2181
|
-
var BarrelManager = class {
|
|
2182
|
-
getFiles({ files: generatedFiles, root }) {
|
|
2183
|
-
const cachedFiles = /* @__PURE__ */ new Map();
|
|
2184
|
-
TreeNode.build(generatedFiles, root)?.forEach((treeNode) => {
|
|
2185
|
-
if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) return;
|
|
2186
|
-
const barrelFile = {
|
|
2187
|
-
path: join(treeNode.parent?.data.path, "index.ts"),
|
|
2188
|
-
baseName: "index.ts",
|
|
2189
|
-
exports: [],
|
|
2190
|
-
imports: [],
|
|
2191
|
-
sources: []
|
|
2192
|
-
};
|
|
2193
|
-
const previousBarrelFile = cachedFiles.get(barrelFile.path);
|
|
2194
|
-
treeNode.leaves.forEach((item) => {
|
|
2195
|
-
if (!item.data.name) return;
|
|
2196
|
-
(item.data.file?.sources || []).forEach((source) => {
|
|
2197
|
-
if (!item.data.file?.path || !source.isIndexable || !source.name) return;
|
|
2198
|
-
if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
|
|
2199
|
-
barrelFile.exports.push({
|
|
2200
|
-
name: [source.name],
|
|
2201
|
-
path: getRelativePath(treeNode.parent?.data.path, item.data.path),
|
|
2202
|
-
isTypeOnly: source.isTypeOnly
|
|
2203
|
-
});
|
|
2204
|
-
barrelFile.sources.push({
|
|
2205
|
-
name: source.name,
|
|
2206
|
-
isTypeOnly: source.isTypeOnly,
|
|
2207
|
-
value: "",
|
|
2208
|
-
isExportable: false,
|
|
2209
|
-
isIndexable: false
|
|
2210
|
-
});
|
|
2211
|
-
});
|
|
2212
|
-
});
|
|
2213
|
-
if (previousBarrelFile) {
|
|
2214
|
-
previousBarrelFile.sources.push(...barrelFile.sources);
|
|
2215
|
-
previousBarrelFile.exports?.push(...barrelFile.exports || []);
|
|
2216
|
-
} else cachedFiles.set(barrelFile.path, barrelFile);
|
|
2217
|
-
});
|
|
2218
|
-
return [...cachedFiles.values()];
|
|
2219
|
-
}
|
|
2220
|
-
};
|
|
2221
|
-
//#endregion
|
|
2222
|
-
//#region src/utils/getBarrelFiles.ts
|
|
2223
|
-
function trimExtName(text) {
|
|
2224
|
-
const dotIndex = text.lastIndexOf(".");
|
|
2225
|
-
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
2226
|
-
return text;
|
|
2227
|
-
}
|
|
2228
|
-
async function getBarrelFiles(files, { type, meta = {}, root, output }) {
|
|
2229
|
-
if (!type || type === "propagate") return [];
|
|
2230
|
-
const barrelManager = new BarrelManager();
|
|
2231
|
-
const pathToBuildFrom = join(root, output.path);
|
|
2232
|
-
if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
|
|
2233
|
-
const barrelFiles = barrelManager.getFiles({
|
|
2234
|
-
files,
|
|
2235
|
-
root: pathToBuildFrom,
|
|
2236
|
-
meta
|
|
2237
|
-
});
|
|
2238
|
-
if (type === "all") return barrelFiles.map((file) => {
|
|
2239
|
-
return {
|
|
2240
|
-
...file,
|
|
2241
|
-
exports: file.exports?.map((exportItem) => {
|
|
2242
|
-
return {
|
|
2243
|
-
...exportItem,
|
|
2244
|
-
name: void 0
|
|
2245
|
-
};
|
|
2246
|
-
})
|
|
2247
|
-
};
|
|
2248
|
-
});
|
|
2249
|
-
return barrelFiles.map((indexFile) => {
|
|
2250
|
-
return {
|
|
2251
|
-
...indexFile,
|
|
2252
|
-
meta
|
|
2253
|
-
};
|
|
2254
|
-
});
|
|
2739
|
+
//#region src/utils/getConfigs.ts
|
|
2740
|
+
/**
|
|
2741
|
+
* Resolves a {@link ConfigInput} into a normalized array of {@link Config} objects.
|
|
2742
|
+
*
|
|
2743
|
+
* - Awaits the config when it is a `Promise`.
|
|
2744
|
+
* - Calls the factory function with `args` when the config is a function.
|
|
2745
|
+
* - Wraps a single config object in an array for uniform downstream handling.
|
|
2746
|
+
*/
|
|
2747
|
+
async function getConfigs(config, args) {
|
|
2748
|
+
const resolved = await (typeof config === "function" ? config(args) : config);
|
|
2749
|
+
return (Array.isArray(resolved) ? resolved : [resolved]).map((item) => ({
|
|
2750
|
+
plugins: [],
|
|
2751
|
+
...item
|
|
2752
|
+
}));
|
|
2255
2753
|
}
|
|
2256
2754
|
//#endregion
|
|
2257
|
-
//#region src/utils/
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2755
|
+
//#region src/utils/getPreset.ts
|
|
2756
|
+
/**
|
|
2757
|
+
* Returns a copy of `defaults` where each function in `userOverrides` is wrapped
|
|
2758
|
+
* so a `null`/`undefined` return falls back to the original. Non-function values
|
|
2759
|
+
* are assigned directly. All calls use the merged object as `this`.
|
|
2760
|
+
*/
|
|
2761
|
+
function withFallback(defaults, userOverrides) {
|
|
2762
|
+
const merged = { ...defaults };
|
|
2763
|
+
for (const key of Object.keys(userOverrides)) {
|
|
2764
|
+
const userVal = userOverrides[key];
|
|
2765
|
+
const defaultVal = defaults[key];
|
|
2766
|
+
if (typeof userVal === "function" && typeof defaultVal === "function") merged[key] = (...args) => userVal.apply(merged, args) ?? defaultVal.apply(merged, args);
|
|
2767
|
+
else if (userVal !== void 0) merged[key] = userVal;
|
|
2768
|
+
}
|
|
2769
|
+
return merged;
|
|
2268
2770
|
}
|
|
2269
|
-
//#endregion
|
|
2270
|
-
//#region src/utils/getConfigs.ts
|
|
2271
2771
|
/**
|
|
2272
|
-
*
|
|
2772
|
+
* Resolves a named preset into a resolver, transformer, and generators.
|
|
2773
|
+
*
|
|
2774
|
+
* - Selects the preset resolver; wraps it with user overrides using null/undefined fallback.
|
|
2775
|
+
* - Composes the preset's transformers into a single visitor; wraps it with the user transformer using null/undefined fallback.
|
|
2776
|
+
* - Combines preset generators with user-supplied generators; falls back to the `default` preset's generators when neither provides any.
|
|
2273
2777
|
*/
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
const
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2778
|
+
function getPreset(params) {
|
|
2779
|
+
const { preset: presetName, presets, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = params;
|
|
2780
|
+
const preset = presets[presetName];
|
|
2781
|
+
const presetResolver = preset?.resolver ?? presets["default"].resolver;
|
|
2782
|
+
const resolver = userResolver ? withFallback(presetResolver, userResolver) : presetResolver;
|
|
2783
|
+
const presetTransformers = preset?.transformers ?? [];
|
|
2784
|
+
const presetTransformer = presetTransformers.length > 0 ? composeTransformers$1(...presetTransformers) : void 0;
|
|
2785
|
+
const transformer = presetTransformer && userTransformer ? withFallback(presetTransformer, userTransformer) : userTransformer ?? presetTransformer;
|
|
2786
|
+
const presetGenerators = preset?.generators ?? [];
|
|
2787
|
+
const defaultGenerators = presets["default"]?.generators ?? [];
|
|
2788
|
+
return {
|
|
2789
|
+
resolver,
|
|
2790
|
+
transformer,
|
|
2791
|
+
generators: presetGenerators.length > 0 || userGenerators.length > 0 ? [...presetGenerators, ...userGenerators] : defaultGenerators,
|
|
2792
|
+
preset
|
|
2793
|
+
};
|
|
2286
2794
|
}
|
|
2287
2795
|
//#endregion
|
|
2288
2796
|
//#region src/utils/linters.ts
|
|
2797
|
+
/**
|
|
2798
|
+
* Returns `true` when the given linter is installed and callable.
|
|
2799
|
+
*
|
|
2800
|
+
* Availability is detected by running `<linter> --version` and checking
|
|
2801
|
+
* that the process exits without error.
|
|
2802
|
+
*/
|
|
2289
2803
|
async function isLinterAvailable(linter) {
|
|
2290
2804
|
try {
|
|
2291
2805
|
await x(linter, ["--version"], { nodeOptions: { stdio: "ignore" } });
|
|
@@ -2294,14 +2808,73 @@ async function isLinterAvailable(linter) {
|
|
|
2294
2808
|
return false;
|
|
2295
2809
|
}
|
|
2296
2810
|
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Detects the first available linter on the current system.
|
|
2813
|
+
*
|
|
2814
|
+
* - Checks in preference order: `biome`, `oxlint`, `eslint`.
|
|
2815
|
+
* - Returns `null` when none are found.
|
|
2816
|
+
*
|
|
2817
|
+
* @example
|
|
2818
|
+
* ```ts
|
|
2819
|
+
* const linter = await detectLinter()
|
|
2820
|
+
* if (linter) {
|
|
2821
|
+
* console.log(`Using ${linter} for linting`)
|
|
2822
|
+
* }
|
|
2823
|
+
* ```
|
|
2824
|
+
*/
|
|
2297
2825
|
async function detectLinter() {
|
|
2298
|
-
|
|
2826
|
+
const linterNames = new Set([
|
|
2299
2827
|
"biome",
|
|
2300
2828
|
"oxlint",
|
|
2301
2829
|
"eslint"
|
|
2302
|
-
])
|
|
2830
|
+
]);
|
|
2831
|
+
for (const linter of linterNames) if (await isLinterAvailable(linter)) return linter;
|
|
2832
|
+
return null;
|
|
2833
|
+
}
|
|
2834
|
+
//#endregion
|
|
2835
|
+
//#region src/utils/packageJSON.ts
|
|
2836
|
+
function getPackageJSONSync(cwd) {
|
|
2837
|
+
const pkgPath = pkg.up({ cwd });
|
|
2838
|
+
if (!pkgPath) return null;
|
|
2839
|
+
return JSON.parse(readSync(pkgPath));
|
|
2840
|
+
}
|
|
2841
|
+
function match(packageJSON, dependency) {
|
|
2842
|
+
const dependencies = {
|
|
2843
|
+
...packageJSON.dependencies || {},
|
|
2844
|
+
...packageJSON.devDependencies || {}
|
|
2845
|
+
};
|
|
2846
|
+
if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
|
|
2847
|
+
const matched = Object.keys(dependencies).find((dep) => dep.match(dependency));
|
|
2848
|
+
return matched ? dependencies[matched] ?? null : null;
|
|
2849
|
+
}
|
|
2850
|
+
function getVersionSync(dependency, cwd) {
|
|
2851
|
+
const packageJSON = getPackageJSONSync(cwd);
|
|
2852
|
+
return packageJSON ? match(packageJSON, dependency) : null;
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Returns `true` when the nearest `package.json` declares a dependency that
|
|
2856
|
+
* satisfies the given semver range.
|
|
2857
|
+
*
|
|
2858
|
+
* - Searches both `dependencies` and `devDependencies`.
|
|
2859
|
+
* - Accepts a string package name or a `RegExp` to match scoped/pattern packages.
|
|
2860
|
+
* - Uses `semver.satisfies` for range comparison; returns `false` when the
|
|
2861
|
+
* version string cannot be coerced into a valid semver.
|
|
2862
|
+
*
|
|
2863
|
+
* @example
|
|
2864
|
+
* ```ts
|
|
2865
|
+
* satisfiesDependency('react', '>=18') // true when react@18.x is installed
|
|
2866
|
+
* satisfiesDependency(/^@tanstack\//, '>=5') // true when any @tanstack/* >=5 is found
|
|
2867
|
+
* ```
|
|
2868
|
+
*/
|
|
2869
|
+
function satisfiesDependency(dependency, version, cwd) {
|
|
2870
|
+
const packageVersion = getVersionSync(dependency, cwd);
|
|
2871
|
+
if (!packageVersion) return false;
|
|
2872
|
+
if (packageVersion === version) return true;
|
|
2873
|
+
const semVer = coerce(packageVersion);
|
|
2874
|
+
if (!semVer) return false;
|
|
2875
|
+
return satisfies(semVer, version);
|
|
2303
2876
|
}
|
|
2304
2877
|
//#endregion
|
|
2305
|
-
export { AsyncEventEmitter, FunctionParams,
|
|
2878
|
+
export { AsyncEventEmitter, FunctionParams, PluginDriver, URLPath, build, build as default, buildDefaultBanner, composeTransformers, createAdapter, createPlugin, createStorage, defaultResolveBanner, defaultResolveFile, defaultResolveFooter, defaultResolveOptions, defaultResolvePath, defineConfig, defineGenerator, defineLogger, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeGenerators, safeBuild, satisfiesDependency, setup };
|
|
2306
2879
|
|
|
2307
2880
|
//# sourceMappingURL=index.js.map
|