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