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