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